+=head1 METHODS
+
+=over 4
+
+=item init_projection
+
+Sets up for future projection of all observables on the report. Currently
+this is limited to 'cust_bill_pkg'.
+
+=cut
+
+sub init_projection {
+ # this is weird special case stuff--some redesign may be needed
+ # to use it for anything else
+ my $self = shift;
+
+ if ( driver_name ne 'Pg' ) {
+ # also database-specific for now
+ die "projection reports not supported on this platform";
+ }
+
+ my %items = map {$_ => 1} @{ $self->{items} };
+ if ($items{'cust_bill_pkg'}) {
+ my $dbh = dbh;
+ # v_ for 'virtual'
+ my @sql = (
+ # could use TEMPORARY TABLE but we're already transaction-protected
+ 'DROP TABLE IF EXISTS v_cust_bill_pkg',
+ 'CREATE TABLE v_cust_bill_pkg ' .
+ '(LIKE cust_bill_pkg,
+ usage numeric(10,2), _date integer, expire integer)',
+ # XXX this should be smart enough to take only the ones with
+ # sdate/edate overlapping the ROI, for performance
+ "INSERT INTO v_cust_bill_pkg (
+ SELECT cust_bill_pkg.*,
+ (SELECT COALESCE(SUM(amount),0) FROM cust_bill_pkg_detail
+ WHERE cust_bill_pkg_detail.billpkgnum = cust_bill_pkg.billpkgnum),
+ cust_bill._date,
+ cust_pkg.expire
+ FROM cust_bill_pkg $cust_bill_pkg_join
+ )",
+ );
+ foreach my $sql (@sql) {
+ warn "[init_projection] $sql\n" if $DEBUG;
+ $dbh->do($sql) or die $dbh->errstr;
+ }
+ }
+}
+
+=item extend_projection START END
+
+Generates data for the next period of projection. This will be called
+for sequential periods where the END of one equals the START of the next
+(with no gaps).
+
+=cut
+
+sub extend_projection {
+ my $self = shift;
+ my ($speriod, $eperiod) = @_;
+ my %items = map {$_ => 1} @{ $self->{items} };
+ if ($items{'cust_bill_pkg'}) {
+ # append, head-to-tail, new line items identical to any that end within the
+ # period (and aren't expiring)
+ my @fields = ( FS::cust_bill_pkg->fields, qw( usage _date expire ) );
+ my $insert_fields = join(',', @fields);
+ #advance (sdate, edate) by one billing period
+ foreach (@fields) {
+ if ($_ eq 'edate') {
+ $_ = '(edate + (edate - sdate)) AS edate' #careful of integer overflow
+ }
+ elsif ($_ eq 'sdate') {
+ $_ = 'edate AS sdate'
+ }
+ elsif ($_ eq 'setup') {
+ $_ = '0 AS setup' #because recurring only
+ }
+ elsif ($_ eq '_date') {
+ $_ = '(_date + (edate - sdate)) AS _date'
+ }
+ }
+ my $select_fields = join(',', @fields);
+ my $dbh = dbh;
+ my $sql =
+ "INSERT INTO v_cust_bill_pkg ($insert_fields)
+ SELECT $select_fields FROM v_cust_bill_pkg
+ WHERE edate >= $speriod AND edate < $eperiod
+ AND recur > 0
+ AND (expire IS NULL OR expire > edate)";
+ warn "[extend_projection] $sql\n" if $DEBUG;
+ my $rows = $dbh->do($sql) or die $dbh->errstr;
+ warn "[extend_projection] $rows rows\n" if $DEBUG;
+ }
+}
+