[freeside-commits] branch FREESIDE_2_3_BRANCH updated. 14dfb3d6c919e6ff91c2f0cbab261dfc27716a4d

Mark Wells mark at 420.am
Sat Jun 9 20:24:50 PDT 2012


The branch, FREESIDE_2_3_BRANCH has been updated
       via  14dfb3d6c919e6ff91c2f0cbab261dfc27716a4d (commit)
      from  1c30055995a24cb90d92248a173627f082cca443 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 14dfb3d6c919e6ff91c2f0cbab261dfc27716a4d
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Jun 9 20:23:33 2012 -0700

    further changes to unearned income report, #13289

diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 4e8201e..c1728c0 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -1064,42 +1064,49 @@ sub _X_show_zero {
 
 =over 4
 
-=item owed_sql [ BEFORE, AFTER, OPTIONS ]
+=item usage_sql
 
-Returns an SQL expression for the amount owed.  BEFORE and AFTER specify
-a date window.  OPTIONS may include 'no_usage' (excludes usage charges)
-and 'setuprecur' (set to "setup" or "recur" to limit to one or the other).
+Returns an SQL expression for the total usage charges in details on
+an item.
 
 =cut
 
-sub owed_sql {
+my $usage_sql =
+  '(SELECT COALESCE(SUM(cust_bill_pkg_detail.amount),0) 
+    FROM cust_bill_pkg_detail 
+    WHERE cust_bill_pkg_detail.billpkgnum = cust_bill_pkg.billpkgnum)';
+
+sub usage_sql { $usage_sql }
+
+# this makes owed_sql, etc. much more concise
+sub charged_sql {
   my ($class, $start, $end, %opt) = @_;
   my $charged = 
     $opt{setuprecur} =~ /^s/ ? 'cust_bill_pkg.setup' :
     $opt{setuprecur} =~ /^r/ ? 'cust_bill_pkg.recur' :
     'cust_bill_pkg.setup + cust_bill_pkg.recur';
 
-  if ( $opt{no_usage} ) {
-    $charged .= ' - ' . $class->usage_sql;
+  if ($opt{no_usage} and $charged =~ /recur/) { 
+    $charged = "$charged - $usage_sql"
   }
 
-  '(' . $charged . 
-  ' - ' . $class->paid_sql($start, $end, %opt) .
-  ' - ' . $class->credited_sql($start, $end, %opt) . ')'
+  $charged;
 }
 
-=item usage_sql
 
-Returns an SQL expression for the total usage charges in details on
-an item.
+=item owed_sql [ BEFORE, AFTER, OPTIONS ]
+
+Returns an SQL expression for the amount owed.  BEFORE and AFTER specify
+a date window.  OPTIONS may include 'no_usage' (excludes usage charges)
+and 'setuprecur' (set to "setup" or "recur" to limit to one or the other).
 
 =cut
 
-sub usage_sql {
+sub owed_sql {
   my $class = shift;
-  "(SELECT COALESCE(SUM(cust_bill_pkg_detail.amount),0) 
-    FROM cust_bill_pkg_detail 
-    WHERE cust_bill_pkg_detail.billpkgnum = cust_bill_pkg.billpkgnum)"
+  '(' . $class->charged_sql(@_) . 
+  ' - ' . $class->paid_sql(@_) .
+  ' - ' . $class->credited_sql(@_) . ')'
 }
 
 =item paid_sql [ BEFORE, AFTER, OPTIONS ]
@@ -1110,32 +1117,55 @@ Returns an SQL expression for the sum of payments applied to this item.
 
 sub paid_sql {
   my ($class, $start, $end, %opt) = @_;
-  $start = $start ? "AND cust_bill_pay._date <= $start" : '';
-  $end   = $end   ? "AND cust_bill_pay._date >  $end"   : '';
+  my $s = $start ? "AND cust_bill_pay._date <= $start" : '';
+  my $e = $end   ? "AND cust_bill_pay._date >  $end"   : '';
   my $setuprecur = 
     $opt{setuprecur} =~ /^s/ ? 'setup' :
     $opt{setuprecur} =~ /^r/ ? 'recur' :
     '';
   $setuprecur &&= "AND setuprecur = '$setuprecur'";
-  "( SELECT COALESCE(SUM(cust_bill_pay_pkg.amount),0)
+
+  my $paid = "( SELECT COALESCE(SUM(cust_bill_pay_pkg.amount),0)
      FROM cust_bill_pay_pkg JOIN cust_bill_pay USING (billpaynum)
      WHERE cust_bill_pay_pkg.billpkgnum = cust_bill_pkg.billpkgnum
-           $start $end $setuprecur )";
+           $s $e$setuprecur )";
+
+  if ( $opt{no_usage} ) {
+    # cap the amount paid at the sum of non-usage charges, 
+    # minus the amount credited against non-usage charges
+    "LEAST($paid, ". 
+      $class->charged_sql($start, $end, %opt) . ' - ' .
+      $class->credited_sql($start, $end, %opt).')';
+  }
+  else {
+    $paid;
+  }
+
 }
 
 sub credited_sql {
   my ($class, $start, $end, %opt) = @_;
-  $start = $start ? "AND cust_credit_bill._date <= $start" : '';
-  $end   = $end   ? "AND cust_credit_bill._date >  $end"   : '';
+  my $s = $start ? "AND cust_credit_bill._date <= $start" : '';
+  my $e = $end   ? "AND cust_credit_bill._date >  $end"   : '';
   my $setuprecur = 
     $opt{setuprecur} =~ /^s/ ? 'setup' :
     $opt{setuprecur} =~ /^r/ ? 'recur' :
     '';
   $setuprecur &&= "AND setuprecur = '$setuprecur'";
-  "( SELECT COALESCE(SUM(cust_credit_bill_pkg.amount),0)
+
+  my $credited = "( SELECT COALESCE(SUM(cust_credit_bill_pkg.amount),0)
      FROM cust_credit_bill_pkg JOIN cust_credit_bill USING (creditbillnum)
      WHERE cust_credit_bill_pkg.billpkgnum = cust_bill_pkg.billpkgnum
-           $start $end $setuprecur )";
+           $s $e $setuprecur )";
+
+  if ( $opt{no_usage} ) {
+    # cap the amount credited at the sum of non-usage charges
+    "LEAST($credited, ". $class->charged_sql($start, $end, %opt).')';
+  }
+  else {
+    $credited;
+  }
+
 }
 
 =back
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index a84f981..f9dd4a2 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -367,28 +367,44 @@ if ( $cgi->param('unearned_now') =~ /^(\d+)$/ ) {
 
   my $usage_sql = FS::cust_bill_pkg->usage_sql;
   push @select, "($usage_sql) AS usage"; # we need this
-  my $owed_sql = FS::cust_bill_pkg->owed_sql($unearned, '', 
-    setuprecur => 'recur', no_usage => 1);
-  push @select, "($owed_sql) AS owed"; # also this
+  my $paid_sql = 'GREATEST(' .
+    FS::cust_bill_pkg->paid_sql($unearned, '', setuprecur => 'recur') .
+    " - $usage_sql, 0)";
+
+  push @select, "$paid_sql AS paid_no_usage"; # need this either way
+
+  if ( $unearned_mode eq 'paid' ) {
+    # then use the amount paid, minus usage charges
+    $unearned_base = $paid_sql;
+  }
+  else {
+    # use the amount billed, minus usage charges and credits
+    $unearned_base = "GREATEST( cust_bill_pkg.recur - ".
+      FS::cust_bill_pkg->credited_sql($unearned, '', setuprecur => 'recur') .
+      " - $usage_sql, 0)";
+      # include only rows that have some non-usage, non-credited portion
+  }
+  # whatever we're using as the base, only show rows where it's positive
+  push @where, "$unearned_base > 0";
 
-  $unearned_base = "(cust_bill_pkg.recur - $usage_sql)";
   my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
   my $elapsed = "GREATEST( $unearned - cust_bill_pkg.sdate, 0 )";
   my $remaining = "(1 - $elapsed/$period)";
 
-  $unearned_sql = "CAST(
-  GREATEST( ( $unearned_base * $remaining ) - $owed_sql, 0 )
-    AS DECIMAL(10,2)
-  )";
+  $unearned_sql = "CAST( $unearned_base * $remaining AS DECIMAL(10,2) )";
   push @select, "$unearned_sql AS unearned_revenue";
 
-  if ( $unearned_mode eq 'billed' ) {
-    # include only rows that have some unearned portion
-    push @where, "$unearned_base > 0";
-  }
-  elsif ( $unearned_mode eq 'paid' ) {
-    # include only those that have some _paid_ unearned portion
-    push @where, "$unearned_sql > 0";
+  # last payment/credit date
+  my %t = (pay => 'cust_bill_pay', credit => 'cust_credit_bill');
+  foreach my $x (qw(pay credit)) {
+    my $table = $t{$x};
+    my $link = $table.'_pkg';
+    my $pkey = dbdef->table($table)->primary_key;
+    my $last_date_sql = "SELECT MAX(_date) 
+    FROM $table JOIN $link USING ($pkey)
+    WHERE $link.billpkgnum = cust_bill_pkg.billpkgnum 
+    AND $table._date <= $unearned";
+    push @select, "($last_date_sql) AS last_$x";
   }
 
 }
diff --git a/httemplate/search/prepaid_income.html b/httemplate/search/prepaid_income.html
index deed33a..d02345e 100644
--- a/httemplate/search/prepaid_income.html
+++ b/httemplate/search/prepaid_income.html
@@ -98,7 +98,7 @@ $dt->add(days => 1); # the day after that
 $dt->subtract(seconds => 1); # the last second of the report day
 $now = $dt->epoch;
 
-my $link = "cust_bill_pkg.cgi?nottax=1;unearned_now=$now;mode=$mode";
+my $link = "unearned_detail.html?date=$now;mode=$mode";
 
 if ( $cgi->param('include_monthly') ) {
   $link .= ';include_monthly=1';
@@ -138,17 +138,22 @@ foreach my $agentnum (@agentnums) {
 
   my( $total, $total_legacy ) = ( 0, 0 );
 
-  # false laziness
-  my $usage_sql = FS::cust_bill_pkg->usage_sql;
-  my $owed_sql = FS::cust_bill_pkg->owed_sql($now, '', 
-    setuprecur => 'recur', no_usage => 1);
-  my $unearned_base = "(cust_bill_pkg.recur - $usage_sql)";
+  my @opt = ($now, '', setuprecur => 'recur', no_usage => 1);
+  # balance owed, recurring only, not including usage charges
+  my $unearned_base;
+  if ( $mode eq 'billed' ) {
+    $unearned_base = '( ' . 
+                     FS::cust_bill_pkg->charged_sql(@opt) . ' - ' .
+                     FS::cust_bill_pkg->credited_sql(@opt) . ' )';
+  } elsif ( $mode eq 'paid' ) {
+    $unearned_base = FS::cust_bill_pkg->paid_sql(@opt);
+  }
   my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
   my $elapsed  = "GREATEST( $now - cust_bill_pkg.sdate, 0 )";
   my $remaining = "(1 - $elapsed/$period)";
 
   my $unearned_sql = "CAST(
-  GREATEST( $unearned_base * $remaining - $owed_sql, 0 )
+  GREATEST( $unearned_base * $remaining, 0 )
     AS DECIMAL(10,2)
   )";
 
@@ -180,6 +185,8 @@ foreach my $agentnum (@agentnums) {
                  AND part_pkg.freq != '0'
                  $where
              ";
+
+             warn $sql;
   my $sth = dbh->prepare($sql) or die dbh->errstr;
   $sth->execute or die $sth->errstr;
   my $total = $sth->fetchrow_arrayref->[0];
diff --git a/httemplate/search/unearned_detail.html b/httemplate/search/unearned_detail.html
new file mode 100644
index 0000000..831625e
--- /dev/null
+++ b/httemplate/search/unearned_detail.html
@@ -0,0 +1,246 @@
+<& elements/search.html,
+  'title'       => emt("Unearned revenue - ".ucfirst($unearned_mode)),
+  'name'        => emt('line items'),
+  'query'       => $query,
+  'count_query' => $count_query,
+  'count_addl'  => [ $money_char. '%.2f total',
+                     $money_char. '%.2f unearned revenue' 
+                   ],
+  'header'      => [ map( {emt $_} 
+    'Description',
+    'Unearned', # depends on mode
+    'Recurring charge', #recur - usage
+    'Owed', #recur - usage - credits - payments
+    'Paid', #payments
+    'Payment date', #of last payment
+    'Credit date', #of last credit
+    'Charge start',
+    'Charge end',
+    'Invoice',
+    'Date'
+    ),
+    FS::UI::Web::cust_header(),
+  ],
+  'fields'      => [
+    #Description
+    sub { $_[0]->pkgnum > 0
+      ? $_[0]->get('pkg')      # possibly use override.pkg
+      : $_[0]->get('itemdesc') # but i think this correct
+    },
+    #Unearned
+    money_sub('unearned_revenue'),
+    #Recurring charge
+    money_sub('recur_no_usage'),
+    #Owed
+    money_sub('owed_no_usage'),
+    #Paid
+    money_sub('paid_no_usage'),
+    #Payment date
+    date_sub('last_pay'),
+    #Credit date
+    date_sub('last_credit'),
+    #Charge start
+    date_sub('sdate'),
+    #Charge end
+    date_sub('edate'),
+    #Invoice
+    'invnum',
+    #Invoice date
+    date_sub('_date'),
+    \&FS::UI::Web::cust_fields,
+  ],
+  'sort_fields' => [
+    'pkg',
+    # SQL expressions work as sort keys...
+    'unearned_revenue',
+    'recur_no_usage',
+    'owed_no_usage',
+    'paid_no_usage',
+    'last_pay',
+    'last_credit',
+    'sdate',
+    'edate',
+    'invnum',
+    '_date',
+  ],
+  'links'       => [
+    ('' x 9),
+    $ilink,
+    $ilink,
+    ( map { $_ ne 'Cust. Status' ? $clink : '' }
+      FS::UI::Web::cust_header()
+    ),
+  ],
+  'align' => 'lrrcrccrc'.  FS::UI::Web::cust_aligns(),
+  'color' => [ 
+    ('' x 11),
+    FS::UI::Web::cust_colors(),
+  ],
+  'style' => [ 
+    ('' x 11),
+    FS::UI::Web::cust_styles(),
+  ],
+&>
+<%init>
+
+# Separated from cust_bill_pkg.cgi to simplify things.
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my $unearned = '';
+my $unearned_mode = '';
+my $unearned_base = '';
+my $unearned_sql = '';
+
+my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
+my ($join_cust, $join_pkg ) = ('', '');
+
+#here is the agent virtualization
+my $agentnums_sql =
+  $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
+  push @where, FS::cust_main->cust_status_sql . " = '$1'";
+}
+
+push @where, "cust_bill._date >= $beginning",
+             "cust_bill._date <= $ending";
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+  push @where, "cust_main.agentnum = $1";
+}
+
+# no pkgclass, no taxclass, no tax location...
+
+# unearned revenue mode
+$cgi->param('date') =~ /^(\d+)$/
+  or die "date required";
+
+$unearned = $1;
+$unearned_mode = $cgi->param('mode');
+
+push @where, "cust_bill_pkg.sdate < $unearned",
+             "cust_bill_pkg.edate > $unearned",
+             "cust_bill_pkg.recur != 0",
+             "part_pkg.freq != '0'";
+
+if ( !$cgi->param('include_monthly') ) {
+  push @where,
+             "part_pkg.freq != '1'",
+             "part_pkg.freq NOT LIKE '%h'",
+             "part_pkg.freq NOT LIKE '%d'",
+             "part_pkg.freq NOT LIKE '%w'";
+}
+
+my @opt = (
+  $unearned, #before this date
+  '',        #after this date
+  setuprecur => 'recur',
+  no_usage => 1
+);
+
+my $charged = FS::cust_bill_pkg->charged_sql(@opt);
+push @select, "($charged) AS recur_no_usage";
+
+my $owed_sql = FS::cust_bill_pkg->owed_sql(@opt);
+push @select, "($owed_sql) AS owed_no_usage";
+
+my $paid_sql = FS::cust_bill_pkg->paid_sql(@opt);
+push @select, "$paid_sql AS paid_no_usage";
+
+if ( $unearned_mode eq 'paid' ) {
+  # then use the amount paid, minus usage charges
+  $unearned_base = $paid_sql;
+}
+else {
+  # use the amount billed, minus usage charges and credits
+  $unearned_base = "( $charged - " . 
+                    FS::cust_bill_pkg->credited_sql(@opt) . ' )';
+}
+# whatever we're using as the base, only show rows where it's positive
+push @where, "$unearned_base > 0";
+
+my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
+my $elapsed = "GREATEST( $unearned - cust_bill_pkg.sdate, 0 )";
+my $remaining = "(1 - $elapsed/$period)";
+
+$unearned_sql = "CAST( $unearned_base * $remaining AS DECIMAL(10,2) )";
+push @select, "$unearned_sql AS unearned_revenue";
+
+# last payment/credit date
+my %t = (pay => 'cust_bill_pay', credit => 'cust_credit_bill');
+foreach my $x (qw(pay credit)) {
+  my $table = $t{$x};
+  my $link = $table.'_pkg';
+  my $pkey = dbdef->table($table)->primary_key;
+  my $last_date_sql = "SELECT MAX(_date) 
+  FROM $table JOIN $link USING ($pkey)
+  WHERE $link.billpkgnum = cust_bill_pkg.billpkgnum 
+  AND $table._date <= $unearned";
+  push @select, "($last_date_sql) AS last_$x";
+}
+
+#no itemdesc
+#no tax report group kludge
+#no tax exemption
+#usage always excluded
+
+# always 'nottax', not 'istax'
+$join_cust =  '        JOIN cust_bill USING ( invnum )
+                  LEFT JOIN cust_main USING ( custnum ) ';
+
+$join_pkg .=  ' LEFT JOIN cust_pkg USING ( pkgnum )
+                LEFT JOIN part_pkg USING ( pkgpart )
+                LEFT JOIN part_pkg AS override
+                  ON pkgpart_override = override.pkgpart ';
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+my $count_query = "SELECT COUNT(DISTINCT billpkgnum), 
+  SUM( $unearned_base ), SUM( $unearned_sql )
+  FROM cust_bill_pkg $join_cust $join_pkg $where";
+
+push @select, 'part_pkg.pkg',
+              'part_pkg.freq',
+              'cust_main.custnum',
+              FS::UI::Web::cust_sql_fields();
+
+my $query = {
+  'table'     => 'cust_bill_pkg',
+  'addl_from' => "$join_cust $join_pkg",
+  'hashref'   => {},
+  'select'    => join(",\n", @select ),
+  'extra_sql' => $where,
+  'order_by'  => 'ORDER BY cust_bill._date, billpkgnum',
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $money_char;
+
+sub money_sub {
+  $conf ||= new FS::Conf;
+  $money_char ||= $conf->config('money_char') || '$';
+  my $field = shift;
+  sub {
+    $money_char . sprintf('%.2f', $_[0]->get($field));
+  };
+}
+
+sub date_sub {
+  my $field = shift;
+  sub {
+    my $value = $_[0]->get($field);
+    $value ? time2str('%b %d %Y', $value) : '';
+  };
+};
+
+</%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/cust_bill_pkg.pm                 |   80 +++++++----
 httemplate/search/cust_bill_pkg.cgi    |   46 ++++--
 httemplate/search/prepaid_income.html  |   21 ++-
 httemplate/search/unearned_detail.html |  246 ++++++++++++++++++++++++++++++++
 4 files changed, 346 insertions(+), 47 deletions(-)
 create mode 100644 httemplate/search/unearned_detail.html




More information about the freeside-commits mailing list