X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fpart_event.pm;h=1c2389989d2d7cf3ba16c7c4b1f68084ad329c61;hp=c98c3f87a4a5cf53be022c44f355061042a42489;hb=8282a324857d658d17061e3f0867d8c7d71b098a;hpb=b5c4237a34aef94976bc343c8d9e138664fc3984 diff --git a/FS/FS/part_event.pm b/FS/FS/part_event.pm index c98c3f87a..1c2389989 100644 --- a/FS/FS/part_event.pm +++ b/FS/FS/part_event.pm @@ -1,18 +1,16 @@ package FS::part_event; +use base qw( FS::m2name_Common FS::option_Common ); use strict; -use vars qw( @ISA $DEBUG ); +use vars qw( $DEBUG ); use Carp qw(confess); use FS::Record qw( dbh qsearch qsearchs ); -use FS::option_Common; -use FS::m2name_Common; use FS::Conf; +use FS::Cursor; use FS::part_event_option; use FS::part_event_condition; use FS::cust_event; -use FS::agent; -@ISA = qw( FS::m2name_Common FS::option_Common ); # FS::Record ); $DEBUG = 0; =head1 NAME @@ -52,7 +50,7 @@ following fields are currently supported: =item event - event name -=item eventtable - table name against which this event is triggered; currently "cust_bill" (the traditional invoice events), "cust_main" (customer events) or "cust_pkg (package events) (or "cust_statement") +=item eventtable - table name against which this event is triggered: one of "cust_main", "cust_bill", "cust_statement", "cust_pkg", "svc_acct". =item check_freq - how often events of this type are checked; currently "1d" (daily) and "1m" (monthly) are recognized. Note that the apprioriate freeside-daily and/or freeside-monthly cron job needs to be in place. @@ -171,21 +169,16 @@ sub _rebless { Returns the conditions associated with this event, as FS::part_event_condition objects (see L) -=cut - -sub part_event_condition { - my $self = shift; - qsearch( 'part_event_condition', { 'eventpart' => $self->eventpart } ); -} - -=item new_cust_event OBJECT +=item new_cust_event OBJECT, [ OPTION => VALUE ] Creates a new customer event (see L) for the provided object. +The only option allowed is 'time', to set the "current" time for the event. + =cut sub new_cust_event { - my( $self, $object ) = @_; + my( $self, $object, %opt ) = @_; confess "**** $object is not a ". $self->eventtable if ref($object) ne "FS::". $self->eventtable; @@ -195,7 +188,8 @@ sub new_cust_event { new FS::cust_event { 'eventpart' => $self->eventpart, 'tablenum' => $object->$pkey(), - '_date' => time, #i think we always want the real "now" here. + #'_date' => time, #i think we always want the real "now" here. + '_date' => ($opt{'time'} || time), 'status' => 'new', }; } @@ -222,13 +216,6 @@ sub reasontext { confess "part_event->reasontext deprecated"; } Returns the associated agent for this event, if any, as an FS::agent object. -=cut - -sub agent { - my $self = shift; - qsearchs('agent', { 'agentnum' => $self->agentnum } ); -} - =item templatename Returns the alternate invoice template name, if any, or false if there is @@ -252,6 +239,127 @@ sub templatename { } } +=item targets OPTIONS + +Returns all objects (of type C, for this object's +C) eligible for processing under this event, as of right now. +The L object used to test event conditions will be +included in each object as the 'cust_event' pseudo-field. + +This is not used in normal event processing (which is done on a +per-customer basis to control timing of pre- and post-billing events) +but can be useful when configuring events. + +=cut + +sub targets { # may want to cursor this also + my $self = shift; + my %opt = @_; + my $time = $opt{'time'} ||= time; + + my $query = $self->_target_query(%opt); + my @objects = qsearch($query); + my @tested_objects; + foreach my $object ( @objects ) { + my $cust_event = $self->new_cust_event($object, 'time' => $time); + next unless $cust_event->test_conditions; + + $object->set('cust_event', $cust_event); + push @tested_objects, $object; + } + @tested_objects; +} + +sub _target_query { + my $self = shift; + my %opt = @_; + my $time = $opt{'time'}; + + my $eventpart = $self->eventpart; + $eventpart =~ /^\d+$/ or die "bad eventpart $eventpart"; + my $eventtable = $self->eventtable; + + # find all objects that meet the conditions for this part_event + my $linkage = ''; + # this is the 'object' side of the FROM clause + if ( $eventtable ne 'cust_main' ) { + $linkage = + ($self->eventtables_cust_join->{$eventtable} || '') . + ' LEFT JOIN cust_main USING (custnum) '; + } + + # this is the 'event' side + my $join = FS::part_event_condition->join_conditions_sql( $eventtable, + 'time' => $time + ); + my $where = FS::part_event_condition->where_conditions_sql( $eventtable, + 'time' => $time + ); + $join = $linkage . + " INNER JOIN part_event ON ( part_event.eventpart = $eventpart ) $join"; + + $where .= ' AND cust_main.agentnum = '.$self->agentnum + if $self->agentnum; + # don't enforce check_freq since this is a special, out-of-order check + # and don't enforce disabled because we want to be able to see targets + # for a disabled event + + { + table => $eventtable, + hashref => {}, + addl_from => $join, + extra_sql => "WHERE $where", + }; +} + + +=item initialize PARAMS + +Identify all objects eligible for this event and create L +records for each of them, as of the present time, with status "initial". When +combined with conditions that prevent an event from running more than once +(at all or within some period), this will exclude any objects that met the +conditions before the event was created. + +If an L object needs to be initialized, it should be created +in a disabled state to avoid running the event prematurely for any existing +objects. C will enable it once all the cust_event records +have been created. + +This may take some time, so it should be run from the job queue. + +=cut + +sub initialize { + my $self = shift; + my $error; + + my $time = time; + + local $FS::UID::AutoCommit = 1; + my $cursor = FS::Cursor->new( $self->_target_query('time' => $time) ); + while (my $object = $cursor->fetch) { + + my $cust_event = $self->new_cust_event($object, 'time' => $time); + next unless $cust_event->test_conditions; + + $cust_event->status('initial'); + $error = $cust_event->insert; + die $error if $error; + } + + # on successful completion only, re-enable the event + if ( $self->disabled ) { + $self->disabled(''); + $error = $self->replace; + die $error if $error; + } + return; +} + +=cut + + =back =head1 CLASS METHODS @@ -272,8 +380,10 @@ sub eventtable_labels { 'cust_pkg' => 'Package', 'cust_bill' => 'Invoice', 'cust_main' => 'Customer', + 'cust_pay' => 'Payment', 'cust_pay_batch' => 'Batch payment', 'cust_statement' => 'Statement', #too general a name here? "Invoice group"? + 'svc_acct' => 'Account service (svc_acct)', ; \%hash @@ -310,8 +420,10 @@ sub eventtable_pkey { 'cust_main' => 'custnum', 'cust_bill' => 'invnum', 'cust_pkg' => 'pkgnum', + 'cust_pay' => 'paynum', 'cust_pay_batch' => 'paybatchnum', 'cust_statement' => 'statementnum', + 'svc_acct' => 'svcnum', }; } @@ -337,6 +449,36 @@ sub eventtables_runorder { shift->eventtables; #same for now } +=item eventtables_cust_join + +Returns a hash reference of SQL expressions to join each eventtable to +a table with a 'custnum' field. + +=cut + +sub eventtables_cust_join { + my %hash = ( + 'svc_acct' => 'LEFT JOIN cust_svc USING (svcnum) LEFT JOIN cust_pkg USING (pkgnum)', + ); + \%hash; +} + +=item eventtables_custnum + +Returns a hash reference of SQL expressions for the 'custnum' field when +I is in effect. The default is "$eventtable.custnum". + +=cut + +sub eventtables_custnum { + my %hash = ( + map({ $_, "$_.custnum" } shift->eventtables), + 'svc_acct' => 'cust_pkg.custnum' + ); + \%hash; +} + + =item check_freq_labels Returns a hash reference of labels for check_freq values, @@ -376,6 +518,35 @@ hashrefs with the following keys: =back +=head1 ADDING NEW EVENTTABLES + +To add an eventtable, you must: + +=over 4 + +=item Add the table to "eventtable_labels" (with a label) and to +"eventtable_pkey" (with its primary key). + +=item If the table doesn't have a "custnum" field of its own (such +as a svc_x table), add a suitable join expression to +eventtables_cust_join and an expression for the final custnum field +to eventtables_custnum. + +=item Create a method named FS::cust_main->$eventtable(): a wrapper +around qsearch() to return all records in the new table belonging to +the cust_main object. This method must accept 'addl_from' and +'extra_sql' arguments in the way qsearch() does. For svc_ tables, +wrap the svc_x() method. + +=item Add it to FS::cust_event->join_sql and search_sql_where so that +search/cust_event.html will find it. + +=item Create a UI link/form to search for events linked to objects +in the new eventtable, using search/cust_event.html. Place this +somewhere appropriate to the eventtable. + +=back + See L for more information. =cut @@ -412,6 +583,7 @@ sub actions { ( map { $_ => $actions{$_} } sort { $actions{$a}->{'default_weight'}<=>$actions{$b}->{'default_weight'} } + # || $actions{$a}->{'description'} cmp $actions{$b}->{'description'} } $class->all_actions( $eventtable ) ); @@ -430,12 +602,28 @@ sub all_actions { keys %actions } +=item process_initialize 'eventpart' => EVENTPART + +Job queue wrapper for "initialize". EVENTPART identifies the +L object to initialize. + +=cut + +sub process_initialize { + my %opt = @_; + my $part_event = + qsearchs('part_event', { eventpart => $opt{'eventpart'}}) + or die "eventpart '$opt{eventpart}' not found!\n"; + $part_event->initialize; +} + =back =head1 SEE ALSO L, L, L, -L, L, L, L, +L, L, L, L, +L, schema.html from the base documentation. =cut