[freeside-commits] branch master updated. 51a648bdf566a6be176b1d7f98a89a2e400481bf

Ivan ivan at 420.am
Wed Jul 31 05:24:46 PDT 2013


The branch, master has been updated
       via  51a648bdf566a6be176b1d7f98a89a2e400481bf (commit)
       via  1f9cad18af5742ee646a56bc0d5a40e6775eabab (commit)
       via  f264598260908d2442fe1aed2ce3784ce51254e6 (commit)
      from  449e3c3b4fe9b4f55950951c0ecc7aaf954e2212 (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 51a648bdf566a6be176b1d7f98a89a2e400481bf
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Jul 31 05:24:27 2013 -0700

    simple A/P

diff --git a/FS/FS/vend_bill.pm b/FS/FS/vend_bill.pm
index 81de694..c8fcdd7 100644
--- a/FS/FS/vend_bill.pm
+++ b/FS/FS/vend_bill.pm
@@ -193,6 +193,7 @@ sub search {
   my ($class, $param) = @_;
 
   my @where = ();
+  my $addl_from = '';
 
   #_date
   if ( $param->{_date} ) {
@@ -202,7 +203,7 @@ sub search {
                  "vend_bill._date <  $ending";
   }
 
-  #_date
+  #payment_date
   if ( $param->{payment_date} ) {
     my($beginning, $ending) = @{$param->{payment_date}};
 
@@ -210,11 +211,17 @@ sub search {
                  "vend_pay._date <  $ending";
   }
 
+  if ( $param->{'classnum'} =~ /^(\d+)$/ ) {
+    #also simplistic, but good for now
+    $addl_from .= ' LEFT JOIN vend_main USING (vendnum) ';
+    push @where, "vend_main.classnum = $1";
+  }
+
   my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
 
   #simplistic, but how we are for now
-  my $addl_from = ' LEFT JOIN vend_bill_pay USING (vendbillnum) '.
-                  ' LEFT JOIN vend_pay      USING (vendpaynum)  ';
+  $addl_from .= ' LEFT JOIN vend_bill_pay USING (vendbillnum) '.
+                ' LEFT JOIN vend_pay      USING (vendpaynum)  ';
 
   my $count_query = "SELECT COUNT(*), SUM(charged) FROM vend_bill $addl_from $extra_sql";
 
diff --git a/httemplate/elements/select-vend_class.html b/httemplate/elements/select-vend_class.html
new file mode 100644
index 0000000..e323988
--- /dev/null
+++ b/httemplate/elements/select-vend_class.html
@@ -0,0 +1,18 @@
+<% include( '/elements/select-table.html',
+                 'table'       => 'vend_class',
+                 'name_col'    => 'classname',
+                 'value'       => $classnum,
+                 'empty_label' => '(none)',
+                 'hashref'     => { 'disabled' => '' },
+                 %opt,
+             )
+%>
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'vend_class'}
+  if $opt{'vend_class'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-vend_class.html b/httemplate/elements/tr-select-vend_class.html
new file mode 100644
index 0000000..b17191b
--- /dev/null
+++ b/httemplate/elements/tr-select-vend_class.html
@@ -0,0 +1,27 @@
+% if ( 0 ) { # scalar(@{ $opt{'vend_class'} }) == 0 ) { 
+
+  <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE="">
+
+% } else { 
+
+  <TR>
+    <TD ALIGN="right"><% $opt{'label'} || 'Vendor class' %></TD>
+    <TD>
+      <% include( '/elements/select-vend_class.html',
+                    'curr_value' => $classnum,
+                    %opt
+                )
+      %>
+    </TD>
+  </TR>
+
+% } 
+
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'vend_class'} ||= [ qsearch( 'vend_class', { disabled=>'' } ) ];
+
+</%init>
diff --git a/httemplate/search/report_vend_bill.html b/httemplate/search/report_vend_bill.html
index defda70..4f391fd 100644
--- a/httemplate/search/report_vend_bill.html
+++ b/httemplate/search/report_vend_bill.html
@@ -4,7 +4,7 @@
 
   <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
 
-   <TR>
+    <TR>
         <TD ALIGN="right" VALIGN="center"><% mt('Date') |h %></TD>
         <TD>
           <TABLE>
@@ -16,7 +16,7 @@
         </TD>
     </TR>
 
-  <TR>
+    <TR>
         <TD ALIGN="right" VALIGN="center"><% mt('Payment date') |h %></TD>
         <TD>
           <TABLE>
@@ -28,6 +28,9 @@
         </TD>
     </TR>
 
+    <& /elements/tr-select-vend_class.html,
+    &>
+
   </TABLE>
 
 <BR>
diff --git a/httemplate/search/vend_bill.html b/httemplate/search/vend_bill.html
index 33eb224..9bc74cf 100644
--- a/httemplate/search/vend_bill.html
+++ b/httemplate/search/vend_bill.html
@@ -40,6 +40,8 @@ $search{'_date'} = [ $beginning, $ending ];
 ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, 'payment_date');
 $search{'payment_date'} = [ $beginning, $ending ];
 
+$search{'classnum'} = $cgi->param('classnum');
+
 my $query = FS::vend_bill->search( \%search );
 my $count_query = delete( $query->{'count_query'} );
 

commit 1f9cad18af5742ee646a56bc0d5a40e6775eabab
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Jul 31 03:57:07 2013 -0700

    simple A/P

diff --git a/FS/FS/vend_bill.pm b/FS/FS/vend_bill.pm
index cd20f95..81de694 100644
--- a/FS/FS/vend_bill.pm
+++ b/FS/FS/vend_bill.pm
@@ -202,15 +202,26 @@ sub search {
                  "vend_bill._date <  $ending";
   }
 
+  #_date
+  if ( $param->{payment_date} ) {
+    my($beginning, $ending) = @{$param->{payment_date}};
+
+    push @where, "vend_pay._date >= $beginning",
+                 "vend_pay._date <  $ending";
+  }
+
   my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
 
-  #my $count_query = "SELECT COUNT(*) FROM vend_bill $addl_from $extra_sql";
-  my $count_query = "SELECT COUNT(*), SUM(charged) FROM vend_bill $extra_sql";
+  #simplistic, but how we are for now
+  my $addl_from = ' LEFT JOIN vend_bill_pay USING (vendbillnum) '.
+                  ' LEFT JOIN vend_pay      USING (vendpaynum)  ';
+
+  my $count_query = "SELECT COUNT(*), SUM(charged) FROM vend_bill $addl_from $extra_sql";
 
   +{
     'table'         => 'vend_bill',
-    #'select'        => $select,
-    #'addl_from'     => $addl_from,
+    'select'        => 'vend_bill.*, vend_pay._date as payment_date',
+    'addl_from'     => $addl_from,
     'hashref'       => {},
     'extra_sql'     => $extra_sql,
     'order_by'      => 'ORDER BY _date',
diff --git a/httemplate/search/report_vend_bill.html b/httemplate/search/report_vend_bill.html
index aec1bba..defda70 100644
--- a/httemplate/search/report_vend_bill.html
+++ b/httemplate/search/report_vend_bill.html
@@ -16,6 +16,17 @@
         </TD>
     </TR>
 
+  <TR>
+        <TD ALIGN="right" VALIGN="center"><% mt('Payment date') |h %></TD>
+        <TD>
+          <TABLE>
+              <& /elements/tr-input-beginning_ending.html,
+                        prefix   => 'payment_date',
+                        layout   => 'horiz',
+              &>
+          </TABLE>
+        </TD>
+    </TR>
 
   </TABLE>
 
diff --git a/httemplate/search/vend_bill.html b/httemplate/search/vend_bill.html
index ddcc0c9..33eb224 100644
--- a/httemplate/search/vend_bill.html
+++ b/httemplate/search/vend_bill.html
@@ -7,6 +7,7 @@
      'count_query' => $count_query,
      'count_addl'  => [ '$%.2f total', ],
      'header'      => [ 'Date',
+                        'Payment date',
                         'Vendor',
                         'Class',
                         'Amount',
@@ -14,6 +15,7 @@
                       ],
      'fields'      => [
                         sub { time2str('%D', shift->_date) },
+                        sub { time2str('%D', shift->payment_date) },
                         sub { shift->vend_main->vendname },
                         sub { shift->vend_main->vend_class->classname },
                         'charged',
@@ -32,9 +34,12 @@ die "access denied"
 my %search = ();
 
 # begin/end/beginning/ending
-my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, '');
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, '_date');
 $search{'_date'} = [ $beginning, $ending ];
 
+($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, 'payment_date');
+$search{'payment_date'} = [ $beginning, $ending ];
+
 my $query = FS::vend_bill->search( \%search );
 my $count_query = delete( $query->{'count_query'} );
 

commit f264598260908d2442fe1aed2ce3784ce51254e6
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Jul 31 03:36:14 2013 -0700

    simple A/P

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 6c12e81..1d4a939 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -346,6 +346,12 @@ if ( -e $addl_handler_use_file ) {
   use FS::agent_currency;
   use FS::currency_exchange;
   use FS::part_pkg_currency;
+  use FS::cust_payby;
+  use FS::vend_main;
+  use FS::vend_class;
+  use FS::vend_bill;
+  use FS::vend_pay;
+  use FS::vend_bill_pay;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index b987963..2ce1794 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -236,6 +236,10 @@ sub dbdef_dist {
 
     }
 
+    my $primary_key_col = $tableobj->column($tableobj->primary_key)
+      or die "$table: primary key declared as ". $tableobj->primary_key.
+             ", but no column of that name\n";
+
     my $historynum_type = ( $tableobj->column($tableobj->primary_key)->type
                               =~ /^(bigserial|bigint|int8)$/i
                                 ? 'bigserial'
@@ -4258,6 +4262,66 @@ sub tables_hashref {
       'index'  => [ [ 'devicepart' ], [ 'svcnum' ], ],
     },
 
+    'vend_main' => {
+      'columns' => [
+        'vendnum',   'serial',     '',      '', '', '',
+        'vendname', 'varchar',     '', $char_d, '', '',
+        'classnum',     'int',     '',      '', '', '',
+        'disabled',    'char', 'NULL',       1, '', '', 
+      ],
+      'primary_key' => 'vendnum',
+      'unique'      => [ ['vendname', 'disabled'] ],
+      'index'       => [],
+    },
+
+    'vend_class' => {
+      'columns' => [
+        'classnum',     'serial',     '',      '', '', '', 
+        'classname',   'varchar',     '', $char_d, '', '', 
+        'disabled',       'char', 'NULL',       1, '', '', 
+      ],
+      'primary_key' => 'classnum',
+      'unique'      => [],
+      'index'       => [ ['disabled'] ],
+    },
+
+    'vend_bill' => {
+      'columns' => [
+        'vendbillnum',    'serial',     '',      '', '', '', 
+        'vendnum',           'int',     '',      '', '', '', 
+        '_date',        @date_type,                  '', '', 
+        'charged',     @money_type,                  '', '', 
+      ],
+      'primary_key' => 'vendbillnum',
+      'unique' => [],
+      'index' => [ ['vendnum'], ['_date'], ],
+    },
+
+    'vend_pay' => {
+      'columns' => [
+        'vendpaynum',   'serial',    '',       '', '', '',
+        'vendnum',         'int',    '',       '', '', '', 
+        '_date',     @date_type,                   '', '', 
+        'paid',      @money_type,                  '', '', 
+      ],
+      'primary_key' => 'vendpaynum',
+      'unique' => [],
+      'index' => [ [ 'vendnum' ], [ '_date' ], ],
+    },
+
+    'vend_bill_pay' => {
+      'columns' => [
+        'vendbillpaynum', 'serial',     '',   '', '', '', 
+        'vendbillnum',       'int',     '',   '', '', '', 
+        'vendpaynum',        'int',     '',   '', '', '', 
+        'amount',  @money_type, '', '', 
+        #? '_date',   @date_type, '', '', 
+      ],
+      'primary_key' => 'vendbillpaynum',
+      'unique' => [],
+      'index' => [ [ 'vendbillnum' ], [ 'vendpaynum' ] ],
+    },
+
     %{ tables_hashref_torrus() },
 
     # tables of ours for doing torrus virtual port combining
diff --git a/FS/FS/class_Common.pm b/FS/FS/class_Common.pm
index 5ee8208..455cb9f 100644
--- a/FS/FS/class_Common.pm
+++ b/FS/FS/class_Common.pm
@@ -68,7 +68,7 @@ returns the error, otherwise returns false.
 
 =item check
 
-Checks all fields to make sure this is a valid package classification.  If
+Checks all fields to make sure this is a valid classification.  If
 there is an error, returns the error, otherwise returns false.  Called by the
 insert and replace methods.
 
diff --git a/FS/FS/vend_bill.pm b/FS/FS/vend_bill.pm
new file mode 100644
index 0000000..cd20f95
--- /dev/null
+++ b/FS/FS/vend_bill.pm
@@ -0,0 +1,234 @@
+package FS::vend_bill;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::vend_main;
+use FS::vend_pay;
+use FS::vend_bill_pay;
+
+=head1 NAME
+
+FS::vend_bill - Object methods for vend_bill records
+
+=head1 SYNOPSIS
+
+  use FS::vend_bill;
+
+  $record = new FS::vend_bill \%hash;
+  $record = new FS::vend_bill { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::vend_bill object represents a vendor invoice or payable.  FS::vend_bill
+inherits from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item vendbillnum
+
+primary key
+
+=item vendnum
+
+vendnum
+
+=item _date
+
+_date
+
+=item charged
+
+charged
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'vend_bill'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+  my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "inserting vend_bill: $error";
+  }
+
+  #magically auto-inserting for the simple case
+  my $vend_pay = new FS::vend_pay {
+    'vendnum'    => $self->vendnum,
+    'vendbillnum' => $self->vendbillnum,
+    '_date'       => $self->get('payment_date') || $self->_date,
+    'paid'        => $self->charged,
+  };
+
+  $error = $vend_pay->insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "auto-inserting vend_pay: $error";
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  foreach my $vend_bill_pay ( $self->vend_bill_pay ) {
+    my $error = $vend_bill_pay->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('vendbillnum')
+    || $self->ut_foreign_key('vendnum', 'vend_main', 'vendnum')
+    || $self->ut_numbern('_date')
+    || $self->ut_money('charged')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item vend_main
+
+=cut
+
+sub vend_main {
+  my $self = shift;
+  qsearchs('vend_main', { 'vendnum', $self->vendnum });
+}
+
+=item vend_bill_pay
+
+=cut
+
+sub vend_bill_pay {
+  my $self = shift;
+  qsearch('vend_bill_pay', { 'vendbillnum', $self->vendbillnum });
+}
+
+=item search
+
+=cut
+
+sub search {
+  my ($class, $param) = @_;
+
+  my @where = ();
+
+  #_date
+  if ( $param->{_date} ) {
+    my($beginning, $ending) = @{$param->{_date}};
+
+    push @where, "vend_bill._date >= $beginning",
+                 "vend_bill._date <  $ending";
+  }
+
+  my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+  #my $count_query = "SELECT COUNT(*) FROM vend_bill $addl_from $extra_sql";
+  my $count_query = "SELECT COUNT(*), SUM(charged) FROM vend_bill $extra_sql";
+
+  +{
+    'table'         => 'vend_bill',
+    #'select'        => $select,
+    #'addl_from'     => $addl_from,
+    'hashref'       => {},
+    'extra_sql'     => $extra_sql,
+    'order_by'      => 'ORDER BY _date',
+    'count_query'   => $count_query,
+    #'extra_headers' => \@extra_headers,
+    #'extra_fields'  => \@extra_fields,
+  };
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/vend_bill_pay.pm b/FS/FS/vend_bill_pay.pm
new file mode 100644
index 0000000..c10c9eb
--- /dev/null
+++ b/FS/FS/vend_bill_pay.pm
@@ -0,0 +1,156 @@
+package FS::vend_bill_pay;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( dbh qsearch ); #qsearchs );
+use FS::vend_bill;
+use FS::vend_pay;
+
+=head1 NAME
+
+FS::vend_bill_pay - Object methods for vend_bill_pay records
+
+=head1 SYNOPSIS
+
+  use FS::vend_bill_pay;
+
+  $record = new FS::vend_bill_pay \%hash;
+  $record = new FS::vend_bill_pay { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::vend_bill_pay object represents the application of a vendor payment to a
+specific invoice or payment. FS::vend_bill_pay inherits from FS::Record.  The
+following fields are currently supported:
+
+=over 4
+
+=item vendbillpaynum
+
+primary key
+
+=item vendbillnum
+
+vendbillnum
+
+=item vendpaynum
+
+vendpaynum
+
+=item amount
+
+amount
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'vend_bill_pay'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  #magically auto-deleting for the simple case
+  foreach my $vend_pay ( $self->vend_pay ) {
+    my $error = $vend_pay->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('vendbillpaynum')
+    || $self->ut_foreign_key('vendbillnum', 'vend_bill', 'vendbillnum')
+    || $self->ut_foreign_key('vendpaynum', 'vend_pay', 'vendpaynum')
+    || $self->ut_money('amount')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item vend_pay
+
+=cut
+
+sub vend_pay {
+  my $self = shift;
+  qsearch('vend_pay', { 'vendpaynum', $self->vendpaynum });
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/vend_class.pm b/FS/FS/vend_class.pm
new file mode 100644
index 0000000..30899e5
--- /dev/null
+++ b/FS/FS/vend_class.pm
@@ -0,0 +1,112 @@
+package FS::vend_class;
+
+use strict;
+use base qw( FS::class_Common );
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::vend_class - Object methods for vend_class records
+
+=head1 SYNOPSIS
+
+  use FS::vend_class;
+
+  $record = new FS::vend_class \%hash;
+  $record = new FS::vend_class { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::vend_class object represents a vendor class.  FS::vend_class inherits
+from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item classnum
+
+primary key
+
+=item classname
+
+classname
+
+=item disabled
+
+disabled
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new vendor class.  To add the vendor class to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'vend_class'; }
+
+sub _target_table { 'vend_main'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid vendor class.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('classnum')
+    || $self->ut_text('classname')
+    || $self->ut_enum('disabled', [ '', 'Y' ])
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/vend_main.pm b/FS/FS/vend_main.pm
new file mode 100644
index 0000000..ef79c00
--- /dev/null
+++ b/FS/FS/vend_main.pm
@@ -0,0 +1,124 @@
+package FS::vend_main;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearchs ); # qsearch qsearchs );
+use FS::vend_class;
+
+=head1 NAME
+
+FS::vend_main - Object methods for vend_main records
+
+=head1 SYNOPSIS
+
+  use FS::vend_main;
+
+  $record = new FS::vend_main \%hash;
+  $record = new FS::vend_main { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::vend_main object represents a vendor.  FS::vend_main inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item vendnum
+
+primary key
+
+=item vendname
+
+vendname
+
+=item classnum
+
+classnum
+
+=item disabled
+
+disabled
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new vendor.  To add the vendor to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'vend_main'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid vendor.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('vendnum')
+    || $self->ut_text('vendname')
+    || $self->ut_foreign_key('classnum', 'vend_class', 'classnum')
+    || $self->ut_enum('disabled', [ '', 'Y' ] )
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item vend_class
+
+=cut
+
+sub vend_class {
+  my $self = shift;
+  qsearchs('vend_class', { 'classnum' => $self->classnum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/vend_pay.pm b/FS/FS/vend_pay.pm
new file mode 100644
index 0000000..3216bdb
--- /dev/null
+++ b/FS/FS/vend_pay.pm
@@ -0,0 +1,152 @@
+package FS::vend_pay;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( dbh ); #qsearch qsearchs );
+use FS::vend_bill_pay;
+
+=head1 NAME
+
+FS::vend_pay - Object methods for vend_pay records
+
+=head1 SYNOPSIS
+
+  use FS::vend_pay;
+
+  $record = new FS::vend_pay \%hash;
+  $record = new FS::vend_pay { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::vend_pay object represents a vendor payment.  FS::vend_pay inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item vendpaynum
+
+primary key
+
+=item vendnum
+
+vendnum
+
+=item _date
+
+_date
+
+=item paid
+
+paid
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new payment.  To add the payment to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'vend_pay'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+=cut
+
+sub insert {
+  my $self = shift;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "inserting vend_pay: $error";
+  }
+
+  if ( $self->get('vendbillnum') ) {
+
+    my $vend_bill_pay = new FS::vend_bill_pay {
+      'vendpaynum'  => $self->vendpaynum,
+      'vendbillnum' => $self->get('vendbillnum'),
+      'amount'      => $self->paid,
+    };
+
+    $error = $vend_bill_pay->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "auto-inserting vend_bill_pay: $error";
+    }
+
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid payment.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('vendpaynum')
+    || $self->ut_foreign_key('vendnum', 'vend_main', 'vendnum')
+    || $self->ut_numbern('_date')
+    || $self->ut_money('paid')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/t/vend_bill.t b/FS/t/vend_bill.t
new file mode 100644
index 0000000..710ff6d
--- /dev/null
+++ b/FS/t/vend_bill.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::vend_bill;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/vend_bill_pay.t b/FS/t/vend_bill_pay.t
new file mode 100644
index 0000000..e6f4cca
--- /dev/null
+++ b/FS/t/vend_bill_pay.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::vend_bill_pay;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/vend_class.t b/FS/t/vend_class.t
new file mode 100644
index 0000000..b7d759e
--- /dev/null
+++ b/FS/t/vend_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::vend_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/vend_main.t b/FS/t/vend_main.t
new file mode 100644
index 0000000..07fbdb4
--- /dev/null
+++ b/FS/t/vend_main.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::vend_main;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/vend_pay.t b/FS/t/vend_pay.t
new file mode 100644
index 0000000..9108733
--- /dev/null
+++ b/FS/t/vend_pay.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::vend_pay;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/vend_class.html b/httemplate/browse/vend_class.html
new file mode 100644
index 0000000..806b278
--- /dev/null
+++ b/httemplate/browse/vend_class.html
@@ -0,0 +1,33 @@
+<& elements/browse.html,
+     'title'              => 'Vendor classes',
+     'html_init'          => $html_init,
+     'name'               => 'vendor classes',
+     'disableable'        => 1,
+     'disabled_statuspos' => 2,
+     'query'              => { 'table'     => 'vend_class',
+                               'hashref'   => {},
+                               'order_by' => 'ORDER BY classnum',
+                             },
+     'count_query'        => $count_query,
+     'header'             => $header,
+     'fields'             => $fields,
+     'links'              => $links,
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+  'Vendor classes group vendor charges for Accounts Payable reporting.<BR><BR>'.
+  qq!<A HREF="${p}edit/vend_class.html"><I>Add a vendor class</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM vend_class';
+
+my $link = [ $p.'edit/vend_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Class' ];
+my $fields = [ 'classnum', 'classname' ];
+my $links  = [ $link, $link ];
+
+</%init>                                                                        
diff --git a/httemplate/browse/vend_main.html b/httemplate/browse/vend_main.html
new file mode 100644
index 0000000..ef1c060
--- /dev/null
+++ b/httemplate/browse/vend_main.html
@@ -0,0 +1,36 @@
+<& elements/browse.html,
+     'title'              => 'Vendors',
+     'html_init'          => $html_init,
+     'name'               => 'vendors',
+     'disableable'        => 1,
+     'disabled_statuspos' => 2,
+     'query'              => { 'table'     => 'vend_main',
+                               'hashref'   => {},
+                               'order_by' => 'ORDER BY vendname',
+                             },
+     'count_query'        => $count_query,
+     'header'             => $header,
+     'fields'             => $fields,
+     'links'              => $links,
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+  #'Vendors.<BR><BR>'.
+  qq!<A HREF="${p}edit/vend_main.html"><I>Add a vendor</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM vend_main';
+
+my $link = [ $p.'edit/vend_main.html?', 'vendnum' ];
+my $clink = [ $p.'edit/vend_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Vendor', 'Class', ];
+my $fields = [ 'vendnum', 'vendname',
+               sub { shift->vend_class->classname; }
+             ];
+my $links  = [ $link, $link, $clink ];
+
+</%init>                                                                        
diff --git a/httemplate/edit/process/vend_class.html b/httemplate/edit/process/vend_class.html
new file mode 100644
index 0000000..6a7add6
--- /dev/null
+++ b/httemplate/edit/process/vend_class.html
@@ -0,0 +1,10 @@
+<& elements/process.html,
+     'table'       => 'vend_class',
+     'viewall_dir' => 'browse',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/vend_main.html b/httemplate/edit/process/vend_main.html
new file mode 100644
index 0000000..3f02e9c
--- /dev/null
+++ b/httemplate/edit/process/vend_main.html
@@ -0,0 +1,10 @@
+<& elements/process.html,
+     'table'       => 'vend_main',
+     'viewall_dir' => 'browse',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/vend_class.html b/httemplate/edit/vend_class.html
new file mode 100644
index 0000000..c04c337
--- /dev/null
+++ b/httemplate/edit/vend_class.html
@@ -0,0 +1,5 @@
+<& elements/class_Common.html,
+     'name_singular' => 'Vendor Class',
+     'table'         => 'vend_class',
+     'nocat'         => 1,
+&>
diff --git a/httemplate/edit/vend_main.html b/httemplate/edit/vend_main.html
new file mode 100644
index 0000000..97a3d0b
--- /dev/null
+++ b/httemplate/edit/vend_main.html
@@ -0,0 +1,27 @@
+<& elements/edit.html,
+     'name_singular' => 'Vendor',
+     'table'         => 'vend_main',
+     'fields'        => [
+                          'vendname',
+                          { field=>'disabled', type=>'checkbox', value=>'Y', },
+                          { field         => 'classnum',
+                            type          => 'select-table',
+                            table         => 'vend_class',
+                            disable_empty => 1,
+                            name_col      => 'classname',
+                          },
+                        ],
+     'labels'        => {
+                          'vendnum'  => 'Vendor number',
+                          'vendname' => 'Vendor',
+                          'classnum' => 'Class',
+                          'disabled' => 'Disabled',
+                        },
+     'viewall_dir'   => 'browse',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index f2ff2ac..a7411b7 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -369,6 +369,10 @@ if( $curuser->access_right('Financial reports') ) {
 
 } # else $report_financial contains nothing.
 
+tie my %report_payable, 'Tie::IxHash',
+  'Payables' => [ $fsurl. 'search/report_vend_bill.html' ],
+;
+
 tie my %report_logs, 'Tie::IxHash';
   $report_logs{'System log'} = [ $fsurl.'search/log.html', 'View system events and debugging information.' ],
   if $curuser->access_right('View system logs')
@@ -405,9 +409,12 @@ $report_menu{'Employees'}      =  [ \%report_employees, 'Employee reports'  ]
   if keys %report_employees;
 $report_menu{'Billing events'} =  [ \%report_bill_event, 'Billing events' ]
   if $curuser->access_right('Billing event reports');
-$report_menu{'Financial'}      = [ \%report_financial, 'Financial reports' ]
+$report_menu{'Financial (Receivables)'} = [ \%report_financial, 'Financial reports (Receivables)' ]
   if $curuser->access_right('Financial reports') 
   or $curuser->access_right('Receivables report');
+$report_menu{'Financial (Payables)'} = [ \%report_payable, 'Financial reports (Payables)' ]
+  if $curuser->access_right('Financial reports');
+
 $report_menu{'Logs'}           = [ \%report_logs, 'System and email logs' ]
   if (keys %report_logs); # empty if the user has no rights to it
 $report_menu{'SQL Query'}      = [ $fsurl.'search/report_sql.html', 'SQL Query']
@@ -694,11 +701,21 @@ $config_menu{'Packages'} = [ \%config_pkg, '' ]
      || $curuser->access_right('Edit package definitions')
      || $curuser->access_right('Edit global package definitions');
 
-if ( $curuser->access_right('Configuration' ) ) {
+if ( $curuser->access_right('Configuration') ) {
   $config_menu{'Services'} = [ \%config_export_svc, '' ];
   $config_menu{separator3} = '';
 }
 
+tie my %config_vendor, 'Tie::IxHash',
+  'Vendor classes' => [ $fsurl.'browse/vend_class.html', '' ],
+  'Vendors'        => [ $fsurl.'browse/vend_main.html', '' ],
+;
+
+if ( $curuser->access_right('Configuration') ) {
+  $config_menu{'Vendors'} = [ \%config_vendor, '' ];
+  $config_menu{separator4} = '';
+}
+
 $config_menu{'Billing'} = [ \%config_billing, '' ]
   if $curuser->access_right('Edit billing events')
   || $curuser->access_right('Edit global billing events');
diff --git a/httemplate/misc/delete-vend_bill.html b/httemplate/misc/delete-vend_bill.html
new file mode 100755
index 0000000..d1d58ce
--- /dev/null
+++ b/httemplate/misc/delete-vend_bill.html
@@ -0,0 +1,20 @@
+% if ( $error ) {
+%   errorpage($error);
+% } else {
+<% $cgi->redirect($p. "search/vend_bill.html") %>
+% }
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#untaint vendbillnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal vendbillnum";
+my $vendbillnum = $1;
+
+my $vend_bill = qsearchs('vend_bill',{'vendbillnum'=>$vendbillnum});
+
+my $error = $vend_bill->delete;
+
+</%init>
diff --git a/httemplate/search/report_vend_bill.html b/httemplate/search/report_vend_bill.html
new file mode 100644
index 0000000..aec1bba
--- /dev/null
+++ b/httemplate/search/report_vend_bill.html
@@ -0,0 +1,34 @@
+<& /elements/header.html, mt('Payables Report') &>
+
+<FORM ACTION="vend_bill.html" METHOD="GET">
+
+  <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+   <TR>
+        <TD ALIGN="right" VALIGN="center"><% mt('Date') |h %></TD>
+        <TD>
+          <TABLE>
+              <& /elements/tr-input-beginning_ending.html,
+                        prefix   => '_date',
+                        layout   => 'horiz',
+              &>
+          </TABLE>
+        </TD>
+    </TR>
+
+
+  </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
+
diff --git a/httemplate/search/vend_bill.html b/httemplate/search/vend_bill.html
new file mode 100644
index 0000000..ddcc0c9
--- /dev/null
+++ b/httemplate/search/vend_bill.html
@@ -0,0 +1,52 @@
+<& elements/search.html,
+     'title'       => 'Payables',
+     'name'        => 'payables',
+     'menubar'     => [ 'Add a payable' => '../edit/vend_bill.html', ],
+     'html_init'   => $html_init,
+     'query'       => $query,
+     'count_query' => $count_query,
+     'count_addl'  => [ '$%.2f total', ],
+     'header'      => [ 'Date',
+                        'Vendor',
+                        'Class',
+                        'Amount',
+                        '',
+                      ],
+     'fields'      => [
+                        sub { time2str('%D', shift->_date) },
+                        sub { shift->vend_main->vendname },
+                        sub { shift->vend_main->vend_class->classname },
+                        'charged',
+                        sub { '<A HREF="javascript:areyousure('.
+                               shift->vendbillnum.
+                              ')">delete</A>';
+                            },
+                      ],
+
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %search = ();
+
+# begin/end/beginning/ending
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, '');
+$search{'_date'} = [ $beginning, $ending ];
+
+my $query = FS::vend_bill->search( \%search );
+my $count_query = delete( $query->{'count_query'} );
+
+my $html_init .= <<"END";
+<SCRIPT TYPE="text/javascript">
+  function areyousure(vendbillnum) {
+    if ( confirm('Are you sure you want to delete this payable?') )
+      window.location.href="${p}misc/delete-vend_bill.html?" + vendbillnum;
+    
+  }
+</SCRIPT>
+END
+
+</%init>
+

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

Summary of changes:
 FS/FS/Mason.pm                                |    6 +
 FS/FS/Schema.pm                               |   64 +++++++
 FS/FS/class_Common.pm                         |    2 +-
 FS/FS/vend_bill.pm                            |  252 +++++++++++++++++++++++++
 FS/FS/vend_bill_pay.pm                        |  156 +++++++++++++++
 FS/FS/vend_class.pm                           |  112 +++++++++++
 FS/FS/vend_main.pm                            |  124 ++++++++++++
 FS/FS/vend_pay.pm                             |  152 +++++++++++++++
 FS/t/vend_bill.t                              |    5 +
 FS/t/vend_bill_pay.t                          |    5 +
 FS/t/vend_class.t                             |    5 +
 FS/t/vend_main.t                              |    5 +
 FS/t/vend_pay.t                               |    5 +
 httemplate/browse/vend_class.html             |   33 ++++
 httemplate/browse/vend_main.html              |   36 ++++
 httemplate/edit/process/vend_class.html       |   10 +
 httemplate/edit/process/vend_main.html        |   10 +
 httemplate/edit/vend_class.html               |    5 +
 httemplate/edit/vend_main.html                |   27 +++
 httemplate/elements/menu.html                 |   21 ++-
 httemplate/elements/select-vend_class.html    |   18 ++
 httemplate/elements/tr-select-vend_class.html |   27 +++
 httemplate/misc/delete-vend_bill.html         |   20 ++
 httemplate/search/report_vend_bill.html       |   48 +++++
 httemplate/search/vend_bill.html              |   59 ++++++
 25 files changed, 1204 insertions(+), 3 deletions(-)
 create mode 100644 FS/FS/vend_bill.pm
 create mode 100644 FS/FS/vend_bill_pay.pm
 create mode 100644 FS/FS/vend_class.pm
 create mode 100644 FS/FS/vend_main.pm
 create mode 100644 FS/FS/vend_pay.pm
 create mode 100644 FS/t/vend_bill.t
 create mode 100644 FS/t/vend_bill_pay.t
 create mode 100644 FS/t/vend_class.t
 create mode 100644 FS/t/vend_main.t
 create mode 100644 FS/t/vend_pay.t
 create mode 100644 httemplate/browse/vend_class.html
 create mode 100644 httemplate/browse/vend_main.html
 create mode 100644 httemplate/edit/process/vend_class.html
 create mode 100644 httemplate/edit/process/vend_main.html
 create mode 100644 httemplate/edit/vend_class.html
 create mode 100644 httemplate/edit/vend_main.html
 create mode 100644 httemplate/elements/select-vend_class.html
 create mode 100644 httemplate/elements/tr-select-vend_class.html
 create mode 100755 httemplate/misc/delete-vend_bill.html
 create mode 100644 httemplate/search/report_vend_bill.html
 create mode 100644 httemplate/search/vend_bill.html




More information about the freeside-commits mailing list