<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
		<id>https://secure.freeside.biz/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mark</id>
		<title>Freeside - User contributions [en]</title>
		<link rel="self" type="application/atom+xml" href="https://secure.freeside.biz/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mark"/>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php/Special:Contributions/Mark"/>
		<updated>2026-06-02T14:43:19Z</updated>
		<subtitle>User contributions</subtitle>
		<generator>MediaWiki 1.27.7</generator>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9651</id>
		<title>Freeside:4:Documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9651"/>
				<updated>2017-02-08T05:43:24Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: add link to billing internals&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Note =&lt;br /&gt;
&lt;br /&gt;
Some documentation links point to earlier versions until things are fully moved over.  The information should still be applicable.&lt;br /&gt;
&lt;br /&gt;
= Installation and upgrades =&lt;br /&gt;
&lt;br /&gt;
== Backend Installation ==&lt;br /&gt;
&lt;br /&gt;
=== Packages ===&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:InstallingOnDebian8|Debian 8 &amp;quot;jessie&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
=== Source ===&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Installation|New Installation]]&lt;br /&gt;
* [[Freeside:3:Documentation:RT Installation|Integrated RT Installation]]&lt;br /&gt;
* [[Freeside:3:Documentation:Torrus Installation|Integrated Torrus Installation]]&lt;br /&gt;
&lt;br /&gt;
== Signup and Self-service installation ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:3:Documentation:Self-Service Installation|Signup/Self-service Installation]]&lt;br /&gt;
* [[Freeside:3:Documentation:Self-Service access without service|Self-Service access without package/service]]&lt;br /&gt;
* [[Freeside:3:Documentation:NG_Self-Service| Next generation self-service portal]] (Work in progress)&lt;br /&gt;
&lt;br /&gt;
== Upgrading ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Upgrading|Upgrading from 3.3 or later to 4.x]]&lt;br /&gt;
* [[Freeside:3:Documentation:Upgrading|Upgrading between 4.x versions]]&lt;br /&gt;
&lt;br /&gt;
= Users =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:3:Documentation:User|User's Guide]] (help wanted)&lt;br /&gt;
* [http://www.sisd.com/~ivan/freeside-slides Training presentation slides]&lt;br /&gt;
&lt;br /&gt;
== Features in v3 and v4 that somehow got documented here ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Appointments]]&lt;br /&gt;
&lt;br /&gt;
= Administrator =&lt;br /&gt;
&lt;br /&gt;
== Reference ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:3:Documentation:Administration|Administrator's Guide]]&lt;br /&gt;
* [[:Category:Freeside:1.9:Documentation:Template|Templates]] used and their fill in variables.&lt;br /&gt;
&lt;br /&gt;
== New features in v4 ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Multi-currency|Multi-currency]]&lt;br /&gt;
* Emails triggered by system log events can be set up at Configuration -&amp;gt; Miscellaneous -&amp;gt; System log emails&lt;br /&gt;
&lt;br /&gt;
== Features in v3 and v4 that somehow got documented here ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Fees|Automated fees]]&lt;br /&gt;
* [[Freeside:4:Documentation:Cacti|Cacti Integration]]&lt;br /&gt;
* [[Freeside:4:Documentation:MagicMail|MagicMail Integration]]&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous ==&lt;br /&gt;
&lt;br /&gt;
* Some old contributed notes on [[Freeside:Documentation:DisasterRecovery|Disaster Recovery]]&lt;br /&gt;
&lt;br /&gt;
= Developer =&lt;br /&gt;
&lt;br /&gt;
== Reference ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:3:Documentation:Developer|Developer's Guide]]&lt;br /&gt;
* [[Freeside:3:Documentation:Billing_Internals|Billing Internals]] (walkthrough of the core invoice generation code)&lt;br /&gt;
&lt;br /&gt;
== New features in v4 ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Developer:Authentication_Plugins|Authentication Plugins]]&lt;br /&gt;
* [[Freeside:4:Documentation:TaxEngine|Tax Engines]]&lt;br /&gt;
&lt;br /&gt;
= Accounting Guide =&lt;br /&gt;
&lt;br /&gt;
*[[Freeside:Documentation:Accounting|Accounting Guide]]&lt;br /&gt;
&lt;br /&gt;
= Misc old stuff =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:Documentation:FAQ|Frequently Asked Questions]]&lt;br /&gt;
* [[3rd_party_software|Third-party software]]&lt;br /&gt;
* [[Freeside:2.1:Documentation:Administration:VoIP:Timed_Rates|Feature: Timed Rates]]&lt;br /&gt;
* [[Freeside:2.1:Documentation:Administration:RT_Workflow|RT workflow features]]&lt;br /&gt;
* [[Freeside:2.1:Documentation:Administration:Tips_and_Tricks|Tips and Tricks]]&lt;br /&gt;
&lt;br /&gt;
= Changelog =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4.0:Changelog|4.0 Changelog]]&lt;br /&gt;
* [[Freeside:4.1:Changelog|4.1 Changelog]]&lt;br /&gt;
* [[Freeside:4.2:Changelog|4.2 Changelog]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Developer:Billing_Internals&amp;diff=9650</id>
		<title>Freeside:3:Documentation:Developer:Billing Internals</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Developer:Billing_Internals&amp;diff=9650"/>
				<updated>2017-02-08T05:41:47Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= FS::cust_main::Billing::bill =&lt;br /&gt;
This is the main method for generating invoices. It's called on a single FS::cust_main, finds any packages that are due to be billed, and creates one or more invoices with the package charges.&lt;br /&gt;
&lt;br /&gt;
It does not handle sending the invoice to the customer, charging their credit card, suspending packages if they're overdue, or anything else of that kind. Those are events run from collect().&lt;br /&gt;
&lt;br /&gt;
== Initial setup ==&lt;br /&gt;
Grab the customer and options. Immediately exit if the customer is “complimentary”. Set the debug level, create a log context, and write a log entry.&lt;br /&gt;
&lt;br /&gt;
   my( $self, %options ) = @_;&lt;br /&gt;
&lt;br /&gt;
   return '' if $self-&amp;gt;complimentary eq 'Y';&lt;br /&gt;
&lt;br /&gt;
   local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG &amp;gt; $DEBUG;&lt;br /&gt;
   my $log = FS::Log-&amp;gt;new('FS::cust_main::Billing::bill');&lt;br /&gt;
   my %logopt = (object =&amp;gt; $self);&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('start', %logopt);&lt;br /&gt;
   warn &amp;quot;$me bill customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Set the billing clock. $time is the date we are “billing on”. For example, freeside-daily -y (bill packages a few days in the future so that invoices can be mailed out early) sets $time. This is almost, but not exactly, the same as changing the system clock.&lt;br /&gt;
&lt;br /&gt;
$invoice_time is the date that will appear on invoices. Normally it's the same as $time; the -n option overrides that and forces it to be the real system time.&lt;br /&gt;
&lt;br /&gt;
$cmp_time is the “comparison time”: a package gets billed if its next-bill date is &amp;lt;= $cmp_time. This is the same as $time unless '''next-bill-ignore-time''' is set, in which case we use the end of the day. Note that this does ''not'' affect certain other important time values such as prorate cutoffs.&lt;br /&gt;
&lt;br /&gt;
Also parse the 'not_pkgpart' option to exclude specific packages from billing.&lt;br /&gt;
&lt;br /&gt;
   my $time = $options{'time'} || time;&lt;br /&gt;
   my $invoice_time = $options{'invoice_time'} || $time;&lt;br /&gt;
&lt;br /&gt;
   my $cmp_time = ( $conf-&amp;gt;exists('next-bill-ignore-time')&lt;br /&gt;
                      ? day_end( $time )&lt;br /&gt;
                      : $time&lt;br /&gt;
                  );&lt;br /&gt;
&lt;br /&gt;
   $options{'not_pkgpart'} ||= {};&lt;br /&gt;
   $options{'not_pkgpart'} = { map { $_ =&amp;gt; 1 }&lt;br /&gt;
                                   split(/\s*,\s*/, $options{'not_pkgpart'})&lt;br /&gt;
                             }&lt;br /&gt;
     unless ref($options{'not_pkgpart'});&lt;br /&gt;
&lt;br /&gt;
Block out interruptions, start a transaction, and lock the customer record (so that packages aren't ordered or status-changed, payments aren't recorded, etc. while doing this). If anything goes wrong during billing, we will rollback and return an error.&lt;br /&gt;
&lt;br /&gt;
   local $SIG{HUP} = 'IGNORE';&lt;br /&gt;
   local $SIG{INT} = 'IGNORE';&lt;br /&gt;
   local $SIG{QUIT} = 'IGNORE';&lt;br /&gt;
   local $SIG{TERM} = 'IGNORE';&lt;br /&gt;
   local $SIG{TSTP} = 'IGNORE';&lt;br /&gt;
   local $SIG{PIPE} = 'IGNORE';&lt;br /&gt;
&lt;br /&gt;
   my $oldAutoCommit = $FS::UID::AutoCommit;&lt;br /&gt;
   local $FS::UID::AutoCommit = 0;&lt;br /&gt;
   my $dbh = dbh;&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('acquiring lock', %logopt);&lt;br /&gt;
   warn &amp;quot;$me acquiring lock on customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
   $self-&amp;gt;select_for_update; #mutex&lt;br /&gt;
&lt;br /&gt;
== Run pre-billing events. ==&lt;br /&gt;
Certain billing event actions are always run just before billing, because they affect the charges that will appear on the bill. These are:&lt;br /&gt;
&lt;br /&gt;
* pkg_discount&lt;br /&gt;
* cust_pkg_fee, cust_fee, and pkg_fee&lt;br /&gt;
* The old “fee” event that created fees as one-time charges.&lt;br /&gt;
&lt;br /&gt;
In this section we call do_cust_event() with a 'stage' parameter to specify only the pre-billing events.&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('running pre-bill events', %logopt);&lt;br /&gt;
   warn &amp;quot;$me running pre-bill events for customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
   my $error = $self-&amp;gt;do_cust_event(&lt;br /&gt;
     'debug'      &amp;lt;nowiki&amp;gt;=&amp;gt; ( $options{'debug'} || 0 ),&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     'time'       &amp;lt;nowiki&amp;gt;=&amp;gt; $invoice_time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     'check_freq' =&amp;gt; $options{'check_freq'},&lt;br /&gt;
     'stage'      &amp;lt;nowiki&amp;gt;=&amp;gt; 'pre-bill',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   )&lt;br /&gt;
     unless $options{no_commit};&lt;br /&gt;
   if ( $error ) {&lt;br /&gt;
     $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
     return $error;&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('done running pre-bill events', %logopt);&lt;br /&gt;
   warn &amp;quot;$me done running pre-bill events for customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
== Identify billable packages and add-on packages. ==&lt;br /&gt;
Set up space for the line items (%cust_bill_pkg), setup and recur totals, and a tax engine. Every time we add a line item to the invoice, we must push it to the line item array, add its setup and recur charges to the totals, and send it to the tax engine.&lt;br /&gt;
&lt;br /&gt;
A single call to bill() can generate multiple invoices. Packages can be eligible or ineligible for automatic payment; that's implemented by splitting non-auto-payment packages off onto a separate invoice. So we'll (potentially) make two invoices here, and need a line items array, subtotals, etc. for each of them. If any package has the “separate_bill” flag, we'll make an additional invoice just for that package.&lt;br /&gt;
&lt;br /&gt;
Also, if the set of packages to bill wasn't specified ('pkg_list'; it's usually not), then use all of the customer's non-canceled packages. We will check later for whether they're due for billing; that's complicated.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;#keep auto-charge and non-auto-charge line items separate&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   my @passes = ( '', 'no_auto' );&lt;br /&gt;
&lt;br /&gt;
   my %cust_bill_pkg = map { $_ =&amp;gt; [] } @passes;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# find the packages which are due for billing, find out how much they are&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;amp; generate invoice database.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my %total_setup   &amp;lt;nowiki&amp;gt;= map { my $z = 0; $_ =&amp;gt; \$z; } @passes;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   my %total_recur   &amp;lt;nowiki&amp;gt;= map { my $z = 0; $_ =&amp;gt; \$z; } @passes;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my @precommit_hooks = ();&lt;br /&gt;
&lt;br /&gt;
   $options{'pkg_list'} ||= [ $self-&amp;gt;ncancelled_pkgs ];  &amp;lt;nowiki&amp;gt;#param checks?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my %tax_engines;&lt;br /&gt;
   my $tax_is_batch = '';&lt;br /&gt;
   foreach (@passes) {&lt;br /&gt;
     $tax_engines{$_} = FS::TaxEngine-&amp;gt;new(cust_main    &amp;lt;nowiki&amp;gt;=&amp;gt; $self,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                           invoice_time =&amp;gt; $invoice_time,&lt;br /&gt;
                                           cancel       &amp;lt;nowiki&amp;gt;=&amp;gt; $options{cancel},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                           estimate     &amp;lt;nowiki&amp;gt;=&amp;gt; $options{estimate},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                          );&lt;br /&gt;
     $tax_is_batch ||= $tax_engines{$_}-&amp;gt;info-&amp;gt;{batch};&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
Here we start the main loop over all packages. Skip any that are excluded by 'not_pkgpart', or by 'no_prepaid'. (freeside-daily sets this, as prepaid packages are billed by another mechanism.) And if there's an explicit 'pkg_list' then use only those.&lt;br /&gt;
&lt;br /&gt;
   foreach my $cust_pkg ( @{ $options{'pkg_list'} } ) {&lt;br /&gt;
&lt;br /&gt;
     next if $options{'not_pkgpart'}-&amp;gt;{$cust_pkg-&amp;gt;pkgpart};&lt;br /&gt;
&lt;br /&gt;
     my $part_pkg = $cust_pkg-&amp;gt;part_pkg;&lt;br /&gt;
&lt;br /&gt;
     next if $options{'no_prepaid'} &amp;amp;&amp;amp; $part_pkg-&amp;gt;is_prepaid;&lt;br /&gt;
&lt;br /&gt;
     $log-&amp;gt;debug('bill package '. $cust_pkg-&amp;gt;pkgnum, %logopt);&lt;br /&gt;
     warn &amp;quot;  bill package &amp;quot;. $cust_pkg-&amp;gt;pkgnum. &amp;quot;\n&amp;quot; if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#? to avoid use of uninitialized value errors... ?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $cust_pkg-&amp;gt;setfield('bill', '')&lt;br /&gt;
       unless defined($cust_pkg-&amp;gt;bill);&lt;br /&gt;
&lt;br /&gt;
Call self_and_bill_linked() to find any billing add-on packages. Add-ons are additional package definitions (part_pkg records, plus all their accessories) that are linked to a “main” package definition, and generate additional charges on the bill. For example, a phone package that has a monthly fee plus long-distance usage billing would have an add-on voip_cdr package to do the usage billing.&lt;br /&gt;
&lt;br /&gt;
Add-on packages always create separate cust_bill_pkg records, with the “pkgpart_override” field referencing the add-on package. Normally, these charges will show separately on the invoice, but they can be “bundled” with the main package by setting part_pkg_link.hidden. At this point we set a flag if this looks like a bundle package, as that will matter later.&lt;br /&gt;
&lt;br /&gt;
     my $real_pkgpart = $cust_pkg-&amp;gt;pkgpart;&lt;br /&gt;
     my %hash = $cust_pkg-&amp;gt;hash;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# we could implement this bit as FS::part_pkg::has_hidden, but we already&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# suffer from performance issues&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $options{has_hidden} = 0;&lt;br /&gt;
     my @part_pkg = $part_pkg-&amp;gt;self_and_bill_linked;&lt;br /&gt;
     $options{has_hidden} = 1 if ($part_pkg[1] &amp;amp;&amp;amp; $part_pkg[1]-&amp;gt;hidden);&lt;br /&gt;
&lt;br /&gt;
== Create line items for each billable package. ==&lt;br /&gt;
As we create line items we'll push them onto $cust_bill_pkg{$pass} (either '' or 'no_auto'). First, balance transfers: if this package is being billed for the first time after a package change, and package balances are enabled, then credit the old package to zero out its balance, and make a line item to carry the balance forward to its successor package.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# if this package was changed from another package,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# and it hasn't been billed since then,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# and package balances are enabled,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     if ( $cust_pkg-&amp;gt;change_pkgnum&lt;br /&gt;
         and $cust_pkg-&amp;gt;change_date &amp;gt;= ($cust_pkg-&amp;gt;last_bill || 0)&lt;br /&gt;
         and $cust_pkg-&amp;gt;change_date &amp;lt;  $invoice_time&lt;br /&gt;
       and $conf-&amp;gt;exists('pkg-balances') )&lt;br /&gt;
     {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# _transfer_balance will also create the appropriate credit&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my @transfer_items = $self-&amp;gt;_transfer_balance($cust_pkg);&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# $part_pkg[0] is the &amp;quot;real&amp;quot; part_pkg&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $pass = ($cust_pkg-&amp;gt;no_auto || $part_pkg[0]-&amp;gt;no_auto) ?&lt;br /&gt;
                   'no_auto' : '';&lt;br /&gt;
       push @{ $cust_bill_pkg{$pass} }, @transfer_items;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# treating this as recur, just because most charges are recur...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       ${$total_recur{$pass}} += $_-&amp;gt;recur foreach @transfer_items;&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# currently not considering separate_bill here, as it's for &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# one-time charges only&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Loop over the part_pkgs (first the “real” package definition, then the add-ons) and decide which invoice to put the charges on. Normally, $pass = '', which will put all charges on a single invoice, but no_auto packages have to go on another invoice, and each package with separate_bill needs yet another (so it will create entries in %total_setup, %total_recur, %cust_bill_pkg, etc.).&lt;br /&gt;
&lt;br /&gt;
     foreach my $part_pkg ( @part_pkg ) {&lt;br /&gt;
&lt;br /&gt;
       my $this_cust_pkg = $cust_pkg;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# for add-on packages, copy the object to avoid leaking changes back to&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# the caller if pkg_list is in use; see RT#73607&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       if ( $part_pkg-&amp;gt;get('pkgpart') != $real_pkgpart ) {&lt;br /&gt;
         $this_cust_pkg = FS::cust_pkg-&amp;gt;new({ %hash });&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
       my $pass = '';&lt;br /&gt;
       if ( $this_cust_pkg-&amp;gt;separate_bill ) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# if no_auto is also set, that's fine. we just need to not have&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# invoices that are both auto and no_auto, and since the package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# gets an invoice all to itself, it will only be one or the other.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $pass = $this_cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
         if (!exists $cust_bill_pkg{$pass}) { # it may not exist yet&lt;br /&gt;
           push @passes, $pass;&lt;br /&gt;
           $total_setup{$pass} = do { my $z = 0; \$z };&lt;br /&gt;
           $total_recur{$pass} = do { my $z = 0; \$z };&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# it also needs its own tax context&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           $tax_engines{$pass} = FS::TaxEngine-&amp;gt;new(&lt;br /&gt;
                                   cust_main    &amp;lt;nowiki&amp;gt;=&amp;gt; $self,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                   invoice_time =&amp;gt; $invoice_time,&lt;br /&gt;
                                   cancel       &amp;lt;nowiki&amp;gt;=&amp;gt; $options{cancel},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                   estimate     &amp;lt;nowiki&amp;gt;=&amp;gt; $options{estimate},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 );&lt;br /&gt;
           $cust_bill_pkg{$pass} = [];&lt;br /&gt;
         }&lt;br /&gt;
       } elsif ( ($this_cust_pkg-&amp;gt;no_auto || $part_pkg-&amp;gt;no_auto) ) {&lt;br /&gt;
         $pass = 'no_auto';&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
The loop here checks whether the package is due for billing, and handles back-billing of multiple cycles. For example, if a monthly package's bill date is more than a month in the past, we'll bill as many months as necessary to bring the package up to date. The loop normally exits once the package bill date is in the future, but will exit after a single pass if we're billing on cancellation, or if freeside-daily -o is in use. It will also exit if there's an error or if the bill date isn't being incremented (as for a one-time charge).&lt;br /&gt;
&lt;br /&gt;
_make_lines() is complex enough to have [#_make_lines its own section of the docs], but it generates a line item and does the following with it:&lt;br /&gt;
&lt;br /&gt;
* appends it to the arrayref in 'line_items' (so, to $cust_bill_pkg{$pass}).&lt;br /&gt;
* adds its setup and recur fees to the running totals&lt;br /&gt;
* informs the tax engine about it (via the add_sale() method)&lt;br /&gt;
* increments the package's last-bill and next-bill dates&lt;br /&gt;
* returns an error if anything goes wrong&lt;br /&gt;
&lt;br /&gt;
       my $next_bill = $this_cust_pkg-&amp;gt;getfield('bill') || 0;&lt;br /&gt;
       my $error;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# let this run once if this is the last bill upon cancellation&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       while ( $next_bill &amp;lt;= $cmp_time or $options{cancel} ) {&lt;br /&gt;
         $error =&lt;br /&gt;
           $self-&amp;gt;_make_lines( 'part_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'cust_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $this_cust_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'precommit_hooks'     &amp;lt;nowiki&amp;gt;=&amp;gt; \@precommit_hooks,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'line_items'          &amp;lt;nowiki&amp;gt;=&amp;gt; $cust_bill_pkg{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'setup'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_setup{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'recur'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_recur{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'tax_engine'          &amp;lt;nowiki&amp;gt;=&amp;gt; $tax_engines{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'time'                &amp;lt;nowiki&amp;gt;=&amp;gt; $time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'real_pkgpart'        &amp;lt;nowiki&amp;gt;=&amp;gt; $real_pkgpart,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'options'             &amp;lt;nowiki&amp;gt;=&amp;gt; \%options,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                             );&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# Stop if anything goes wrong&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if $error;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# or if we're not incrementing the bill date.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if ($this_cust_pkg-&amp;gt;getfield('bill') || 0) == $next_bill;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# or if we're letting it run only once&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if $options{cancel};&lt;br /&gt;
&lt;br /&gt;
         $next_bill = $this_cust_pkg-&amp;gt;getfield('bill') || 0;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;#stop if -o was passed to freeside-daily&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if $options{'one_recur'};&lt;br /&gt;
       }&lt;br /&gt;
       if ($error) {&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return $error;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     } #foreach my $part_pkg&lt;br /&gt;
&lt;br /&gt;
   } #foreach my $cust_pkg&lt;br /&gt;
&lt;br /&gt;
== Identify applicable fees and tax adjustments and create line items. ==&lt;br /&gt;
At this point we have a list of package line items for each invoice the customer might receive. Now loop through the (possible) invoices and process non-package fees.&lt;br /&gt;
&lt;br /&gt;
A “fee origin” (FS::FeeOrigin_Mixin) is an instruction or “order” to charge a fee&amp;lt;nowiki&amp;gt;; the classes are FS::cust_event_fee (fees generated by billing events, &amp;lt;/nowiki&amp;gt;such as late fees) and FS::cust_pkg_reason_fee (unsuspension fees). When the fee is put on an invoice, the order is fulfilled; the billpkgnum of the fee line item is then recorded on the fee origin record.&lt;br /&gt;
&lt;br /&gt;
The by_cust() method does a search for all fee origins for a customer; we limit it to records with billpkgnum = NULL to get fees that still need to be charged. Those are @pending_fees.&lt;br /&gt;
&lt;br /&gt;
We process fees at this stage (after generating all package line items) because we need to know if the customer is going to receive an invoice or not. Fees can be defined (via the 'nextbill' flag) to be charged immediately, or on the customer's next bill. If all pending fees have 'nextbill' and there aren't any package charges on this invoice (@cust_bill_pkg is empty) then skip to the next invoice. If the customer has no package charges at all, then the 'nextbill' fees won't be charged at all on this billing date and will remain pending.&lt;br /&gt;
&lt;br /&gt;
   foreach my $pass (@passes) { # keys %cust_bill_pkg )&lt;br /&gt;
&lt;br /&gt;
     my @cust_bill_pkg = _omit_zero_value_bundles(@{ $cust_bill_pkg{$pass} });&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;$me billing pass $pass\n&amp;quot;&lt;br /&gt;
            &amp;lt;nowiki&amp;gt;#.Dumper(\@cust_bill_pkg).&amp;quot;\n&amp;quot;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       if $DEBUG &amp;gt; 2;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# process fees&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
     my @pending_fees = FS::FeeOrigin_Mixin-&amp;gt;by_cust($self-&amp;gt;custnum,&lt;br /&gt;
       hashref =&amp;gt; { 'billpkgnum' =&amp;gt; '' }&lt;br /&gt;
     );&lt;br /&gt;
     warn &amp;quot;$me found pending fees:\n&amp;quot;.Dumper(\@pending_fees).&amp;quot;\n&amp;quot;&lt;br /&gt;
       if @pending_fees and $DEBUG &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# determine whether to generate an invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     my $generate_bill = scalar(@cust_bill_pkg) &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
     foreach my $fee (@pending_fees) {&lt;br /&gt;
       $generate_bill = 1 unless $fee-&amp;gt;nextbill;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# don't create an invoice with no line items, or where the only line &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# items are fees that are supposed to be held until the next invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     next if !$generate_bill;&lt;br /&gt;
&lt;br /&gt;
For each fee origin, do some things. Skip the fee if the fee definition is somehow inappropriate:&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# calculate fees...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     my @fee_items;&lt;br /&gt;
     foreach my $fee_origin (@pending_fees) {&lt;br /&gt;
       my $part_fee = $fee_origin-&amp;gt;part_fee;&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# check whether the fee is applicable before doing anything expensive:&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# if the fee def belongs to a different agent, don't charge the fee.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# event conditions should prevent this, but just in case they don't,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# skip the fee.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       if ( $part_fee-&amp;gt;agentnum and $part_fee-&amp;gt;agentnum != $self-&amp;gt;agentnum ) {&lt;br /&gt;
         warn &amp;quot;tried to charge fee#&amp;quot;.$part_fee-&amp;gt;feepart .&lt;br /&gt;
              &amp;quot; on customer#&amp;quot;.$self-&amp;gt;custnum.&amp;quot; from a different agent.\n&amp;quot;;&lt;br /&gt;
         next;&lt;br /&gt;
       }&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# also skip if it's disabled&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       next if $part_fee-&amp;gt;disabled eq 'Y';&lt;br /&gt;
&lt;br /&gt;
Then determine the “basis” for the fee, in case it's defined as a percentage of something. The fee origin may declare that the fee applies to a previous invoice (such as a finance charge). Otherwise, the fee applies to the ''current'' invoice. Since the current invoice doesn't exist yet, construct one temporarily that has the line items of the current invoice.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# Decide which invoice to base the fee on.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $cust_bill = $fee_origin-&amp;gt;cust_bill;&lt;br /&gt;
       if (!$cust_bill) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# Then link it to the current invoice. This isn't the real cust_bill&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# object that will be inserted--in particular there are no taxes yet.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# If you want to charge a fee on the total invoice amount including&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# taxes, you have to put the fee on the next invoice.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $cust_bill = FS::cust_bill-&amp;gt;new({&lt;br /&gt;
             'custnum'       &amp;lt;nowiki&amp;gt;=&amp;gt; $s&amp;lt;/nowiki&amp;gt;elf-&amp;gt;custnum,&lt;br /&gt;
             'cust_bill_pkg' =&amp;gt; \@cust_bill_pkg,&lt;br /&gt;
             'charged'       &amp;lt;nowiki&amp;gt;=&amp;gt; ${ $total_setup{$pass} } +&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                ${ $total_recur{$pass} },&lt;br /&gt;
         });&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# If the origin is for a specific package, then only apply the fee to&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# line items from that package.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         if ( my $cust_pkg = $fee_origin-&amp;gt;cust_pkg ) {&lt;br /&gt;
           my @charge_fee_on_item;&lt;br /&gt;
           my $charge_fee_on_amount = 0;&lt;br /&gt;
           foreach (@cust_bill_pkg) {&lt;br /&gt;
             if ($_-&amp;gt;pkgnum == $cust_pkg-&amp;gt;pkgnum) {&lt;br /&gt;
               push @charge_fee_on_item, $_;&lt;br /&gt;
               $charge_fee_on_amount += $_-&amp;gt;setup + $_-&amp;gt;recur;&lt;br /&gt;
             }&lt;br /&gt;
           }&lt;br /&gt;
           $cust_bill-&amp;gt;set('cust_bill_pkg', \@charge_fee_on_item);&lt;br /&gt;
           $cust_bill-&amp;gt;set('charged', $charge_fee_on_amount);&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
       } # $cust_bill is now set&lt;br /&gt;
&lt;br /&gt;
FS::part_fee-&amp;gt;lineitem() asks the fee definition to make a cust_bill_pkg record for this fee, as applied to a specific invoice. After the invoice is inserted, we're going to need to record the billpkgnum on the fee origin record. FS::cust_bill_pkg-&amp;gt;insert knows to do this.&lt;br /&gt;
&lt;br /&gt;
Then append any fee line items to the invoice, add them to the running totals, and send them to the tax engine.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# calculate the fee&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $fee_item = $part_fee-&amp;gt;lineitem($cust_bill) or next;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# link this so that we can clear the marker on inserting the line item&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $fee_item-&amp;gt;set('fee_origin', $fee_origin);&lt;br /&gt;
       push @fee_items, $fee_item;&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# add fees to the invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     foreach my $fee_item (@fee_items) {&lt;br /&gt;
&lt;br /&gt;
       push @cust_bill_pkg, $fee_item;&lt;br /&gt;
       ${ $total_setup{$pass} } += $fee_item-&amp;gt;setup;&lt;br /&gt;
       ${ $total_recur{$pass} } += $fee_item-&amp;gt;recur;&lt;br /&gt;
&lt;br /&gt;
       my $part_fee = $fee_item-&amp;gt;part_fee;&lt;br /&gt;
       my $fee_location = $self-&amp;gt;ship_location; # I think?&lt;br /&gt;
&lt;br /&gt;
       my $error = $tax_engines{''}-&amp;gt;add_sale($fee_item);&lt;br /&gt;
&lt;br /&gt;
       return $error if $error;&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Add the special “postal invoice fee”. This was a per-invoice flat fee for sending customers a printed invoice; like all “old” fees it worked by adding a one-time charge. charge_postal_fee() is the method to do that. It adds a one-time package to the customer ''in the middle of billing'', after all their other packages have been billed, for exactly the same reason that other fees are processed at this point in billing. This feature is obsolete so I won't say any more.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# XXX implementation of fees is supposed to make this go away...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     if ( scalar( grep { $_-&amp;gt;recur &amp;amp;&amp;amp; $_-&amp;gt;recur &amp;gt; 0 } @cust_bill_pkg) ||&lt;br /&gt;
            !$conf-&amp;gt;exists('postal_invoice-recurring_only')&lt;br /&gt;
        )&lt;br /&gt;
     {&lt;br /&gt;
&lt;br /&gt;
       my $postal_pkg = $self-&amp;gt;charge_postal_fee();&lt;br /&gt;
       if ( $postal_pkg &amp;amp;&amp;amp; !ref( $postal_pkg ) ) {&lt;br /&gt;
&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return &amp;quot;can't charge postal invoice fee for customer &amp;quot;.&lt;br /&gt;
           $self-&amp;gt;custnum. &amp;quot;: $postal_pkg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
       } elsif ( $postal_pkg ) {&lt;br /&gt;
&lt;br /&gt;
         my $real_pkgpart = $postal_pkg-&amp;gt;pkgpart;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# we could implement this bit as FS::part_pkg::has_hidden, but we already&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# suffer from performance issues&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $options{has_hidden} = 0;&lt;br /&gt;
         my @part_pkg = $postal_pkg-&amp;gt;part_pkg-&amp;gt;self_and_bill_linked;&lt;br /&gt;
         $options{has_hidden} = 1 if ($part_pkg[1] &amp;amp;&amp;amp; $part_pkg[1]-&amp;gt;hidden);&lt;br /&gt;
&lt;br /&gt;
         foreach my $part_pkg ( @part_pkg ) {&lt;br /&gt;
           my %postal_options = %options;&lt;br /&gt;
           delete $postal_options{cancel};&lt;br /&gt;
           my $error =&lt;br /&gt;
             $self-&amp;gt;_make_lines( 'part_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'cust_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $postal_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'precommit_hooks'     &amp;lt;nowiki&amp;gt;=&amp;gt; \@precommit_hooks,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'line_items'          &amp;lt;nowiki&amp;gt;=&amp;gt; \@cust_bill_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'setup'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_setup{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'recur'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_recur{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'tax_engine'          &amp;lt;nowiki&amp;gt;=&amp;gt; $tax_engines{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'time'                &amp;lt;nowiki&amp;gt;=&amp;gt; $time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'real_pkgpart'        &amp;lt;nowiki&amp;gt;=&amp;gt; $real_pkgpart,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'options'             &amp;lt;nowiki&amp;gt;=&amp;gt; \%postal_options,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               );&lt;br /&gt;
           if ($error) {&lt;br /&gt;
             $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
             return $error;&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# it's silly to have a zero value postal_pkg, but....&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         @cust_bill_pkg = _omit_zero_value_bundles(@cust_bill_pkg);&lt;br /&gt;
&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Tax adjustments (FS::cust_tax_adjustment) are manual adjustment records that work much like fee origins, but are created manually. If a tax adjustment exists for the customer, and doesn't have a billpkgnum yet, and an invoice is being generated, then create a line item with the amount and itemdesc specified in the tax adjustment. Note that this line item ''is a tax line item'': it has pkgnum = 0, feepart = NULL, and recur = 0. On that line item, set 'cust_tax_adjustment' to the tax adjustment record so that cust_bill_pkg-&amp;gt;insert can record that the adjustment was billed.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#add tax adjustments&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#XXX does this work with batch tax engines?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     warn &amp;quot;adding tax adjustments...\n&amp;quot; if $DEBUG &amp;gt; 2;&lt;br /&gt;
     foreach my $cust_tax_adjustment (&lt;br /&gt;
       qsearch('cust_tax_adjustment', { 'custnum'    &amp;lt;nowiki&amp;gt;=&amp;gt; $self-&amp;gt;custnum,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                        'billpkgnum' =&amp;gt; '',&lt;br /&gt;
                                      }&lt;br /&gt;
              )&lt;br /&gt;
     ) {&lt;br /&gt;
&lt;br /&gt;
       my $tax = sprintf('%.2f', $cust_tax_adjustment-&amp;gt;amount );&lt;br /&gt;
&lt;br /&gt;
       my $itemdesc = $cust_tax_adjustment-&amp;gt;taxname;&lt;br /&gt;
       $itemdesc = '' if $itemdesc eq 'Tax';&lt;br /&gt;
&lt;br /&gt;
       push @cust_bill_pkg, new FS::cust_bill_pkg {&lt;br /&gt;
         'pkgnum'      &amp;lt;nowiki&amp;gt;=&amp;gt; 0,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'setup'       &amp;lt;nowiki&amp;gt;=&amp;gt; $tax,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'recur'       &amp;lt;nowiki&amp;gt;=&amp;gt; 0,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'sdate'       &amp;lt;nowiki&amp;gt;=&amp;gt; '',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'edate'       &amp;lt;nowiki&amp;gt;=&amp;gt; '',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'itemdesc'    &amp;lt;nowiki&amp;gt;=&amp;gt; $itemdesc,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'itemcomment' =&amp;gt; $cust_tax_adjustment-&amp;gt;comment,&lt;br /&gt;
         'cust_tax_adjustment' =&amp;gt; $cust_tax_adjustment,&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;#'cust_bill_pkg_tax_location' =&amp;gt; \@cust_bill_pkg_tax_location,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       };&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
== Create the invoice record. ==&lt;br /&gt;
Before storing the record, calculate some totals:&lt;br /&gt;
&lt;br /&gt;
* cust_bill.charged: the sum of charges on this invoice (taxes will be added later)&lt;br /&gt;
* billing_balance: the customer's balance before this invoice.&lt;br /&gt;
* previous_balance: the customer's balance immediately after their previous invoice, determined as (billing_balance + charged) on that invoice.&lt;br /&gt;
&lt;br /&gt;
Note that ''billing_balance and previous_balance are obsolete''. They were historically used to create invoice summary sections (“Balance on previous invoice”, “Payments/credits since then”), but since 2014 we actually look at the transaction history. This method produces correct results if transactions get voided.&lt;br /&gt;
&lt;br /&gt;
     my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } );&lt;br /&gt;
&lt;br /&gt;
     my $balance = $self-&amp;gt;balance;&lt;br /&gt;
&lt;br /&gt;
     my $previous_bill = qsearchs({ 'table'     &amp;lt;nowiki&amp;gt;=&amp;gt; 'cust_bill',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                    'hashref'   &amp;lt;nowiki&amp;gt;=&amp;gt; { custnum=&amp;gt;$self-&amp;gt;custnum },&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                    'extra_sql' =&amp;gt; 'ORDER BY _date DESC LIMIT 1',&lt;br /&gt;
                                 });&lt;br /&gt;
     my $previous_balance =&lt;br /&gt;
       $previous_bill&lt;br /&gt;
         ? ( $previous_bill-&amp;gt;billing_balance + $previous_bill-&amp;gt;charged )&lt;br /&gt;
         : 0;&lt;br /&gt;
&lt;br /&gt;
Now store the record. FS::cust_bill::insert() will notice the arrayref of line items and insert all of them. Line items with 'fee_origin' or 'tax_adjustment' references will also trigger updates to those records.&lt;br /&gt;
&lt;br /&gt;
The 'no_commit' option is for simulating billing, and will stop the record from being inserted. Currently this is used only with term prepayment discounts (which are no longer a supported feature) to figure out the “amount saved” over the prepaid term.&lt;br /&gt;
&lt;br /&gt;
Note that (like almost anywhere else in Freeside) $FS::UID::AutoCommit = 0 will also prevent bill() from committing its changes. This is how quotations work: FS::quotation-&amp;gt;estimate opens a transaction, inserts packages, runs bill(), and then reverts the whole transaction.&lt;br /&gt;
&lt;br /&gt;
     $log-&amp;gt;debug('creating the new invoice', %logopt);&lt;br /&gt;
     warn &amp;quot;creating the new invoice\n&amp;quot; if $DEBUG;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#create the new invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     my $cust_bill = new FS::cust_bill ( {&lt;br /&gt;
       'custnum'             &amp;lt;nowiki&amp;gt;=&amp;gt; $self-&amp;gt;custnum,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       '_date'               &amp;lt;nowiki&amp;gt;=&amp;gt; $invoice_time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'charged'             &amp;lt;nowiki&amp;gt;=&amp;gt; $charged,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'billing_balance'     &amp;lt;nowiki&amp;gt;=&amp;gt; $balance,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'previous_balance'    &amp;lt;nowiki&amp;gt;=&amp;gt; $previous_balance,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'invoice_terms'       &amp;lt;nowiki&amp;gt;=&amp;gt; $options{'invoice_terms'},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'cust_bill_pkg'       &amp;lt;nowiki&amp;gt;=&amp;gt; \@cust_bill_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'pending'             &amp;lt;nowiki&amp;gt;=&amp;gt; 'Y', # clear this after doing taxes&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     } );&lt;br /&gt;
&lt;br /&gt;
     if (!$options{no_commit}) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# probably we ought to insert it as pending, and then rollback&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# without ever un-pending it&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $error = $cust_bill-&amp;gt;insert;&lt;br /&gt;
       if ( $error ) {&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return &amp;quot;can't create invoice for customer #&amp;quot;. $self-&amp;gt;custnum. &amp;quot;: $error&amp;quot;;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
== Calculate taxes and insert tax line items. ==&lt;br /&gt;
$tax_is_batch was previously set to true if we're using a batch tax processor. In that case, don't do any of this; just leave the invoice flagged as pending and FS::Cron::tax_batch will do the rest.&lt;br /&gt;
&lt;br /&gt;
Otherwise: get the tax engine we created for this invoice pass, and call calculate_taxes(), passing the pending invoice. This will return an arrayref of line items, or throw an exception. If it throws an exception, rollback and exit.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# calculate and append taxes&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     if ( ! $tax_is_batch) {&lt;br /&gt;
       local $@;&lt;br /&gt;
       my $arrayref = eval { $tax_engines{$pass}-&amp;gt;calculate_taxes($cust_bill) };&lt;br /&gt;
&lt;br /&gt;
       if ( $@ ) {&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return $@;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
Append each tax line item to the invoice (directly, by setting invnum and inserting them) and add the charges to a running total. Tax line items have 'setup' but not 'recur' charges.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# or should this be in TaxEngine?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $total_tax = 0;&lt;br /&gt;
       foreach my $taxline ( @$arrayref ) {&lt;br /&gt;
         $total_tax += $taxline-&amp;gt;setup;&lt;br /&gt;
         $taxline-&amp;gt;set('invnum' =&amp;gt; $cust_bill-&amp;gt;invnum); # just to be sure&lt;br /&gt;
         push @cust_bill_pkg, $taxline; # for return_bill&lt;br /&gt;
&lt;br /&gt;
         if (!$options{no_commit}) {&lt;br /&gt;
           my $error = $taxline-&amp;gt;insert;&lt;br /&gt;
           if ( $error ) {&lt;br /&gt;
             $dbh-&amp;gt;rollback if $oldAutoCommit;&lt;br /&gt;
             return $error;&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
Add the total tax to the total invoice charges, remove the pending flag, and then update the invoice record. Then we're done.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# add tax to the invoice amount and finalize it&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       ${ $total_setup{$pass} } = sprintf('%.2f', ${ $total_setup{$pass} } + $total_tax);&lt;br /&gt;
       $charged = sprintf('%.2f', $charged + $total_tax);&lt;br /&gt;
       $cust_bill-&amp;gt;set('charged', $charged);&lt;br /&gt;
       $cust_bill-&amp;gt;set('pending', '');&lt;br /&gt;
&lt;br /&gt;
       if (!$options{no_commit}) {&lt;br /&gt;
         my $error = $cust_bill-&amp;gt;replace;&lt;br /&gt;
         if ( $error ) {&lt;br /&gt;
           $dbh-&amp;gt;rollback if $oldAutoCommit;&lt;br /&gt;
           return $error;&lt;br /&gt;
         }&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     } # if !$tax_is_batch&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# if it IS batch, then we'll do all this in process_tax_batch&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After dealing with this invoice, push it to the 'return_bill' arrayref if there is one. This will allow the caller to see which invoices were created if they want.&lt;br /&gt;
&lt;br /&gt;
     push @{$options{return_bill}}, $cust_bill if $options{return_bill};&lt;br /&gt;
&lt;br /&gt;
   } #foreach my $pass ( keys %cust_bill_pkg )&lt;br /&gt;
&lt;br /&gt;
Run any post-invoicing pre-commit hooks. These are currently used for only one thing: for packages with time-limited discounts, we need to increment the number of discount months used after completely billing the package, but before doing anything else. FS::part_pkg::discount_Mixin sets this up.&lt;br /&gt;
&lt;br /&gt;
Then commit changes if needed, and return.&lt;br /&gt;
&lt;br /&gt;
   foreach my $hook ( @precommit_hooks ) {&lt;br /&gt;
     eval {&lt;br /&gt;
       &amp;amp;{$hook}; #($self) ?&lt;br /&gt;
     } unless $options{no_commit};&lt;br /&gt;
     if ( $@ ) {&lt;br /&gt;
       $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
       return &amp;quot;$@ running precommit hook $hook\n&amp;quot;;&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   $dbh-&amp;gt;commit or die $dbh-&amp;gt;errstr if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
&lt;br /&gt;
   ''; #no error&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
= FS::cust_main::Billing::_make_lines =&lt;br /&gt;
This is the other most important billing method. It takes a single package and package definition, and creates at most one line item for that package. This is called only from bill() and should probably never be used any other way, since it updates the package's billing dates.&lt;br /&gt;
&lt;br /&gt;
== Gather parameters ==&lt;br /&gt;
$cust_pkg is the package. $part_pkg is the package definition, either the “real” one for this package or a billing add-on. We temporarily set the cust_pkg-&amp;gt;pkgpart property to match it so that we can call cust_pkg methods that delegate to “$self-&amp;gt;part_pkg”. We then copy all the cust_pkg's fields into %hash.&lt;br /&gt;
&lt;br /&gt;
$time is the “billing as of” date. %options is all the options that were passed to bill().&lt;br /&gt;
&lt;br /&gt;
The other $param{} elements are accumulators passed from outside so that this method can add stuff to them.&lt;br /&gt;
&lt;br /&gt;
$cust_location here isn't used for anything, because of tax engine refactoring.&lt;br /&gt;
&lt;br /&gt;
The reference to freq_override is one last attempt to make sure term discounts aren't set up in a radically absurd way.&lt;br /&gt;
&lt;br /&gt;
$lineitems is for remembering if we've done anything that requires creating a line item; if not then we'll exit without pushing one to the array.&lt;br /&gt;
&lt;br /&gt;
@details will be used for any detail records that we attach to this line item. Those are additional lines of text shown below the line item on the invoice.&lt;br /&gt;
&lt;br /&gt;
$cmp_time, as in bill(), is either “right now” or “the end of today” depending on the '''next-bill-ignore-time''' option.&lt;br /&gt;
&lt;br /&gt;
 sub _make_lines {&lt;br /&gt;
   my ($self, %params) = @_;&lt;br /&gt;
&lt;br /&gt;
   local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG &amp;gt; $DEBUG;&lt;br /&gt;
&lt;br /&gt;
   my $part_pkg = $params{part_pkg} or die &amp;quot;no part_pkg specified&amp;quot;;&lt;br /&gt;
   my $cust_pkg = $params{cust_pkg} or die &amp;quot;no cust_pkg specified&amp;quot;;&lt;br /&gt;
   my $cust_location = $cust_pkg-&amp;gt;tax_location;&lt;br /&gt;
   my $precommit_hooks = $params{precommit_hooks} or die &amp;quot;no precommit_hooks specified&amp;quot;;&lt;br /&gt;
   my $cust_bill_pkgs = $params{line_items} or die &amp;quot;no line buffer specified&amp;quot;;&lt;br /&gt;
   my $total_setup = $params{setup} or die &amp;quot;no setup accumulator specified&amp;quot;;&lt;br /&gt;
   my $total_recur = $params{recur} or die &amp;quot;no recur accumulator specified&amp;quot;;&lt;br /&gt;
   my $time = $params{'time'} or die &amp;quot;no time specified&amp;quot;;&lt;br /&gt;
   my (%options) = %{$params{options}};&lt;br /&gt;
&lt;br /&gt;
   my $tax_engine = $params{tax_engine};&lt;br /&gt;
&lt;br /&gt;
   if ( $part_pkg-&amp;gt;freq ne '1' and ($options{'freq_override'} || 0) &amp;gt; 0 ) {&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# this should never happen&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     die 'freq_override billing attempted on non-monthly package '.&lt;br /&gt;
       $cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   my $dbh = dbh;&lt;br /&gt;
   my $real_pkgpart = $params{real_pkgpart};&lt;br /&gt;
   my %hash = $cust_pkg-&amp;gt;hash;&lt;br /&gt;
   my $old_cust_pkg = new FS::cust_pkg \%hash;&lt;br /&gt;
&lt;br /&gt;
   my @details = ();&lt;br /&gt;
   my $lineitems = 0;&lt;br /&gt;
&lt;br /&gt;
   $cust_pkg-&amp;gt;pkgpart($part_pkg-&amp;gt;pkgpart);&lt;br /&gt;
&lt;br /&gt;
   my $cmp_time = ( $conf-&amp;gt;exists('next-bill-ignore-time')&lt;br /&gt;
                      ? day_end( $time )&lt;br /&gt;
                      : $time&lt;br /&gt;
                  );&lt;br /&gt;
&lt;br /&gt;
== Charge setup fee and set setup date ==&lt;br /&gt;
Setup fees are charged the first time a package is billed. %setup_param will be passed to the calc_setup() method; because of how discounts work, it needs to know if this is an add-on package, and to report if it's applying any discounts. $setup and $unitsetup will become those fields on the cust_bill_pkg record.&lt;br /&gt;
&lt;br /&gt;
The conditions for charging the setup fee are exactly as described in the inline comment. Note that a package that's canceled before first billing will never be charged a setup fee (or any other fee). We assume the package was ordered by mistake.&lt;br /&gt;
&lt;br /&gt;
Note also that if the package has an expire date &amp;lt;= $cmp_time then it won't be charged. Normally the package would be canceled already. This check is for the case where the package isn't expired yet (in real time) but is being billed for a date after it will expire. For example, if a package has a next bill date of Feb 1, but an expiration date of Jan 31, and you're running freeside-daily -y 3 to print invoices 3 days in advance, then billing will trigger for the customer on Jan 28. The package won't be canceled yet. However, we should ''not'' preprint an invoice for this package because it ''will'' expire before its real billing date arrives. This is a real case that has come up.&lt;br /&gt;
&lt;br /&gt;
Possibly we should check adjourn dates also, since a suspended package is not supposed to get set up.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# bill setup&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my $setup = 0;&lt;br /&gt;
   my $unitsetup = 0;&lt;br /&gt;
   my @setup_discounts = ();&lt;br /&gt;
   my %setup_param = ( 'discounts'     &amp;lt;nowiki&amp;gt;=&amp;gt; \@setup_discounts,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                       'real_pkgpart'  &amp;lt;nowiki&amp;gt;=&amp;gt; $params{real_pkgpart}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                     );&lt;br /&gt;
   my $setup_billed_currency = '';&lt;br /&gt;
   my $setup_billed_amount = 0;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# Conditions for setting setup date and charging the setup fee:&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - this is not a recurring-only billing run&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and the package is not currently being canceled&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and, unless we're specifically told otherwise via 'resetup':&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - it doesn't already HAVE a setup date&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or a start date in the future&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - and it's not suspended&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and it doesn't have an expire date in the past&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# The &amp;quot;disable_setup_suspended&amp;quot; option is now obsolete; we never set the&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# setup date on a suspended package.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   if (     ! $options{recurring_only}&lt;br /&gt;
        and ! $options{cancel}&lt;br /&gt;
        and ( $options{'resetup'}&lt;br /&gt;
              || ( ! $cust_pkg-&amp;gt;setup&lt;br /&gt;
                   &amp;amp;&amp;amp; ( ! $cust_pkg-&amp;gt;start_date&lt;br /&gt;
                        || $cust_pkg-&amp;gt;start_date &amp;lt;= $cmp_time&lt;br /&gt;
                      )&lt;br /&gt;
                   &amp;amp;&amp;amp; ( ! $cust_pkg-&amp;gt;getfield('susp') )&lt;br /&gt;
                 )&lt;br /&gt;
            )&lt;br /&gt;
        and ( ! $cust_pkg-&amp;gt;expire&lt;br /&gt;
              || $cust_pkg-&amp;gt;expire &amp;gt; $cmp_time )&lt;br /&gt;
      )&lt;br /&gt;
   {&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;    bill setup\n&amp;quot; if $DEBUG &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
If 'waive_setup' is set on the package then skip this step.&lt;br /&gt;
&lt;br /&gt;
Calculate the setup fee. FS::cust_pkg-&amp;gt;calc_setup just delegates to its part_pkg (which we've overridden if we're processing an add-on package right now). The following part_pkg classes have a calc_setup method:&lt;br /&gt;
&lt;br /&gt;
* flat and recur_Common: Calls prorate_setup(), about which [#Deferred prorate see below]. Adds any “additional_info” strings to @details (this is used for putting notes on one-time charges). Calls base_setup() to get the setup fee from the package definition, then calc_discount() to apply any discounts, then multiplies the result by quantity. Almost all packages use this logic.&lt;br /&gt;
* currency_fixed: Calls prorate_setup(), then base_setup() (which does the currency conversion). Oddly, it doesn't multiply by quantity; this is probably a bug, since it does multiply by quantity in calc_recur().&lt;br /&gt;
* delayed_Mixin: If the 'delay_setup' option is off, pushes back the package bill date by some number of days so that the start of recurring billing is delayed. Then calls whatever calc_setup() method would otherwise apply.&lt;br /&gt;
* rt_field and (the obsolete) voip_sqlradacct: Returns the 'setup_fee' package option, without multiplying by quantity or applying discounts.&lt;br /&gt;
&lt;br /&gt;
Also calculate the per-unit setup fee (for displaying on the invoice). The base_setup() method, by definition, returns the non-discounted per-unit setup fee.&lt;br /&gt;
&lt;br /&gt;
Currency packages also return (via $setup_param) the currency and amount before doing conversion; we remember those here and will put them on the invoice later.&lt;br /&gt;
&lt;br /&gt;
     unless ( $cust_pkg-&amp;gt;waive_setup ) {&lt;br /&gt;
         $lineitems++;&lt;br /&gt;
&lt;br /&gt;
         $setup = eval { $cust_pkg-&amp;gt;calc_setup( $time, \@details, \%setup_param ) };&lt;br /&gt;
         return &amp;quot;$@ running calc_setup for $cust_pkg\n&amp;quot;&lt;br /&gt;
           if $@;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# Only increment unitsetup here if there IS a setup fee.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# prorate_defer_bill may cause calc_setup on a setup-stage package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# to return zero, and the setup fee to be charged later. (This happens&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# when it's first billed on the prorate cutoff day. RT#31276.)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         if ( $setup ) {&lt;br /&gt;
           $unitsetup = $cust_pkg-&amp;gt;base_setup()&lt;br /&gt;
                          || $setup; #XXX uuh&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
         if ( $setup_param{'billed_currency'} ) {&lt;br /&gt;
           $setup_billed_currency = delete $setup_param{'billed_currency'};&lt;br /&gt;
           $setup_billed_amount   &amp;lt;nowiki&amp;gt;= delete $setup_param{'billed_amount'};&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Assign the setup date. The package may already have a setup date (because of 'resetup'). Otherwise, if it has a designated start date, use that (and clear the start date). Otherwise, it started billing now so set it to now.&lt;br /&gt;
&lt;br /&gt;
     if ( $cust_pkg-&amp;gt;get('setup') ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# don't change it&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     } elsif ( $cust_pkg-&amp;gt;get('start_date') ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# this allows start_date to be used to set the first bill date&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $cust_pkg-&amp;gt;set('setup', $cust_pkg-&amp;gt;get('start_date'));&lt;br /&gt;
     } else {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# if unspecified, start it right now&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $cust_pkg-&amp;gt;set('setup', $time);&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $cust_pkg-&amp;gt;setfield('start_date', '')&lt;br /&gt;
       if $cust_pkg-&amp;gt;start_date;&lt;br /&gt;
&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
=== Deferred prorate ===&lt;br /&gt;
This is an optional mode for prorate packages (and flat monthly packages using sync_bill_date), enabled by the prorate_defer_bill option. Rather than charging a fractional month at setup time, the package is billed on the first cutoff day for a full month ''plus'' the fractional month preceding the cutoff day.&lt;br /&gt;
&lt;br /&gt;
In packages that are eligible for deferred prorate, calc_setup() calls the calc_prorate() method, which checks whether the flag is present. If so, calc_prorate sets the setup date (to now) and the next bill date (to the upcoming cutoff day) but doesn't set last_bill because the package hasn't really been billed. Then it returns a true value, which tells calc_setup ''not'' to charge a setup fee. The package will then not be charged a recurring fee either, because the next bill date is in the future.&lt;br /&gt;
&lt;br /&gt;
(If the setup date is itself a cutoff day, then calc_prorate will ignore the flag and bill the package for a full month as usual. It is highly recommended that you use the prorate_round_day option in combination with this so that the “full month” really is a full month.)&lt;br /&gt;
&lt;br /&gt;
When the first recurring billing date arrives, calc_recur() notices that there's a bill date but no last_bill, and charges for the partial month in the past and full month in the future. (The start and end dates on the line item will reflect this.) It will also call calc_setup() and apply any setup fee defined by the package.&lt;br /&gt;
&lt;br /&gt;
== Charge the recurring fee and adjust the bill date ==&lt;br /&gt;
The comment here explains the logic for whether to charge a recurring fee.&lt;br /&gt;
&lt;br /&gt;
At this point, if it still has a start_date, then either the start_date is in the future or the package couldn't start billing for some other reason (like that it's on hold).&lt;br /&gt;
&lt;br /&gt;
See the note above about the expire date; this part is slightly different in that if the package actually ''has'' expired and we're billing it on cancellation, then charging a recurring fee is allowed.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# bill recurring fee&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;### &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my $recur = 0;&lt;br /&gt;
   my $unitrecur = 0;&lt;br /&gt;
   my @recur_discounts = ();&lt;br /&gt;
   my $recur_billed_currency = '';&lt;br /&gt;
   my $recur_billed_amount = 0;&lt;br /&gt;
   my $sdate;&lt;br /&gt;
&lt;br /&gt;
   my $override_quantity;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# Conditions for billing the recurring fee:&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - the package doesn't have a future start date&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and it's not suspended&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - unless suspend_bill is enabled on the package or package def&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;    - but still not, if the package is on hold&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's suspended for a delayed cancellation&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and its next bill date is in the past&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it doesn't have a next bill date yet&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's a one-time charge&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's a CDR plan with the &amp;quot;bill_every_call&amp;quot; option&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's being canceled&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and it doesn't have an expire date in the past (this can happen with&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  advance billing)&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - again, unless it's being canceled&lt;br /&gt;
   if (     ! $cust_pkg-&amp;gt;start_date&lt;br /&gt;
        and&lt;br /&gt;
            ( ! $cust_pkg-&amp;gt;susp&lt;br /&gt;
                || ( $cust_pkg-&amp;gt;susp != $cust_pkg-&amp;gt;order_date&lt;br /&gt;
                       &amp;amp;&amp;amp; (    $cust_pkg-&amp;gt;option('suspend_bill',1)&lt;br /&gt;
                            || ( $part_pkg-&amp;gt;option('suspend_bill', 1)&lt;br /&gt;
                                  &amp;amp;&amp;amp; ! $cust_pkg-&amp;gt;option('no_suspend_bill',1)&lt;br /&gt;
                               )&lt;br /&gt;
                          )&lt;br /&gt;
                   )&lt;br /&gt;
                || $cust_pkg-&amp;gt;is_status_delay_cancel&lt;br /&gt;
            )&lt;br /&gt;
        and&lt;br /&gt;
             ( $part_pkg-&amp;gt;freq ne '0' &amp;amp;&amp;amp; ( $cust_pkg-&amp;gt;bill || 0 ) &amp;lt;= $cmp_time )&lt;br /&gt;
          || ( $part_pkg-&amp;gt;plan eq 'voip_cdr'&lt;br /&gt;
                &amp;amp;&amp;amp; $part_pkg-&amp;gt;option('bill_every_call')&lt;br /&gt;
             )&lt;br /&gt;
          || $options{cancel}&lt;br /&gt;
&lt;br /&gt;
        and&lt;br /&gt;
           ( ! $cust_pkg-&amp;gt;expire&lt;br /&gt;
             || $cust_pkg-&amp;gt;expire &amp;gt; $cmp_time&lt;br /&gt;
             || $options{cancel}&lt;br /&gt;
           )&lt;br /&gt;
   ) {&lt;br /&gt;
&lt;br /&gt;
Reset the package's usage cap if it has one:&lt;br /&gt;
&lt;br /&gt;
* For voip_cdr, this is for “usage pools”. It clears all cdr_cust_pkg_usage records from the package and resets the cust_pkg_usage's minutes remaining.&lt;br /&gt;
* For flat (and other flat-like packages), this finds svc_accts attached to the package and resets their byte usage counters. This is for RADIUS services with data caps.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# XXX should this be a package event? &amp;lt;/nowiki&amp;gt; probably.  events are called&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# at collection time at the moment, though...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $part_pkg-&amp;gt;reset_usage($cust_pkg, 'debug'=&amp;gt;$DEBUG)&lt;br /&gt;
       if $part_pkg-&amp;gt;can('reset_usage') &amp;amp;&amp;amp; !$options{'no_usage_reset'};&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#don't want to reset usage just cause we want a line item??&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#&amp;amp;&amp;amp; $part_pkg-&amp;gt;pkgpart == $real_pkgpart;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At this point we've decided to charge a recurring fee, so increment $lineitems.&lt;br /&gt;
&lt;br /&gt;
“$sdate” is the effective billing date. It represents the end of the previous cycle and the start of the next. The next bill date will be set to $sdate + one billing period.&lt;br /&gt;
&lt;br /&gt;
For a new package, $sdate is the setup date (so, the start date if there was one); if it's been billed before then it's the billing date.&lt;br /&gt;
&lt;br /&gt;
Prorate math will normally charge for the period from $sdate to the next prorate cycle date after that. After that, it will set the next bill date to the prorate cycle date, so that future billing cyles run from one prorate cycle date to the next. The result is that the customer is charged for a partial month, then for full months after that.&lt;br /&gt;
&lt;br /&gt;
$increment_next_bill is, yes, a flag saying to increment the bill date. We won't increment for one-time charges, or packages that are being canceled. The check for the bill date &amp;lt;= $cmp_time is now redundant, since we won't bill the package at all.&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;    bill recur\n&amp;quot; if $DEBUG &amp;gt; 1;&lt;br /&gt;
     $lineitems++;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# XXX shared with $recur_prog&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $sdate = ( $options{cancel} ? $cust_pkg-&amp;gt;last_bill : $cust_pkg-&amp;gt;bill )&lt;br /&gt;
              || $cust_pkg-&amp;gt;setup&lt;br /&gt;
              || $time;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#over two params! &amp;lt;/nowiki&amp;gt; lets at least switch to a hashref for the rest...&lt;br /&gt;
     my $increment_next_bill = ( $part_pkg-&amp;gt;freq ne '0'&lt;br /&gt;
                                 &amp;amp;&amp;amp; ( $cust_pkg-&amp;gt;getfield('bill') || 0 ) &amp;lt;= $cmp_time&lt;br /&gt;
                                 &amp;amp;&amp;amp; !$options{cancel}&lt;br /&gt;
                               );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The most important part of the entire module: calculate the recurring fee. Call FS::part_pkg::calc_recur (or calc_cancel, if we're canceling the package).&lt;br /&gt;
&lt;br /&gt;
This is ''not a well-behaved interface''. The arguments are all passed by reference (they're used to pass values back out), and they include the contents of %setup_param, so packages can use the %param hashref argument to pass notes between the calc_setup and calc_recur stages. Discounts, especially, use this.&lt;br /&gt;
&lt;br /&gt;
* $sdate is the effective bill date, @details is the array of line item details, and %param contains everything else.&lt;br /&gt;
* 'discounts' is an arrayref of objects that will be inserted with the line item to track which discounts applied.&lt;br /&gt;
* 'precommit_hooks' and 'real_pkgpart' are used to keep track of the months remaining on term-limited discounts: if real_pkgpart matches the pkgpart of the package, then it's not an add-on package, so a month (or other amount) should be deducted. In that case a callback will be appended to precommit_hooks to do that at the end.&lt;br /&gt;
* 'freq_override' should be ignored.&lt;br /&gt;
* 'setup_fee' allows calc_recur to charge a setup fee. This seems bizarre but is needed for certain prorate cases.&lt;br /&gt;
* 'increment_next_bill' is checked in recur_Common to decide whether to apply the fixed recurring charge for voip_cdr and other usage-based packages. The intent seems to be to not bill those charges on cancellation.&lt;br /&gt;
&lt;br /&gt;
     my %param = ( %setup_param,&lt;br /&gt;
                   'precommit_hooks'     &amp;lt;nowiki&amp;gt;=&amp;gt; $precommit_hooks,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                   'increment_next_bill' =&amp;gt; $increment_next_bill,&lt;br /&gt;
                   'discounts'           &amp;lt;nowiki&amp;gt;=&amp;gt; \@recur_discounts,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                   'real_pkgpart'        &amp;lt;nowiki&amp;gt;=&amp;gt; $real_pkgpart,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                   'freq_override' =&amp;gt; $options{freq_override} || '',&lt;br /&gt;
                   'setup_fee'           &amp;lt;nowiki&amp;gt;=&amp;gt; 0,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                 );&lt;br /&gt;
&lt;br /&gt;
     my $method = $options{cancel} ? 'calc_cancel' : 'calc_recur';&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# There may be some part_pkg for which this is wrong. &amp;lt;/nowiki&amp;gt; Only those&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# which can_discount are supported.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# (the UI should prevent adding discounts to these at the moment)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;calling $method on cust_pkg &amp;quot;. $cust_pkg-&amp;gt;pkgnum.&lt;br /&gt;
          &amp;quot; for pkgpart &amp;quot;. $cust_pkg-&amp;gt;pkgpart.&lt;br /&gt;
          &amp;quot; with params &amp;quot;. join(' / ', map &amp;quot;$_=&amp;gt;$param{$_}&amp;quot;, keys %param). &amp;quot;\n&amp;quot;&lt;br /&gt;
       if $DEBUG &amp;gt; 2;&lt;br /&gt;
&lt;br /&gt;
     $recur = eval { $cust_pkg-&amp;gt;$method( \$sdate, \@details, \%param ) };&lt;br /&gt;
     return &amp;quot;$@ running $method for $cust_pkg\n&amp;quot;&lt;br /&gt;
       if ( $@ );&lt;br /&gt;
&lt;br /&gt;
Handle a special case where calc_cancel() finds that the package isn't eligible to be billed on cancellation. This affects the decision about whether to create a line item at all.&lt;br /&gt;
&lt;br /&gt;
     if ($recur eq 'NOTHING') {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# then calc_cancel (or calc_recur but that's not used) has declined to&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# generate a recurring lineitem at all. treat this as zero, but also &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# try not to generate a lineitem.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $recur = 0;&lt;br /&gt;
       $lineitems--;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Process more things returned by calc_recur. Ask the package for the unit recurring charge (passing $sdate because packages with an intro rate will report a different amount in the intro period).&lt;br /&gt;
&lt;br /&gt;
If calc_recur did a currency conversion, record how it happened.&lt;br /&gt;
&lt;br /&gt;
If calc_recur reported a quantity, record that quantity and adjust unitrecur. sql_external packages can do this.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#base_cancel???&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $unitrecur = $cust_pkg-&amp;gt;base_recur( \$sdate ) || $recur; #XXX uuh, better&lt;br /&gt;
&lt;br /&gt;
     if ( $param{'billed_currency'} ) {&lt;br /&gt;
       $recur_billed_currency = delete $param{'billed_currency'};&lt;br /&gt;
       $recur_billed_amount   &amp;lt;nowiki&amp;gt;= delete $param{'billed_amount'};&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     if ( $param{'override_quantity'} ) {&lt;br /&gt;
       $override_quantity = $param{'override_quantity'};&lt;br /&gt;
       $unitrecur = $recur / $override_quantity;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
The other most important part of the module: increment the next bill date so that the package cycles.&lt;br /&gt;
&lt;br /&gt;
As always in Freeside, adding some period to a bill date is done by the FS::part_pkg-&amp;gt;add_freq method.&lt;br /&gt;
&lt;br /&gt;
Deal with the case where this is a supplemental package. A supplemental package is “geared” to its main package: there's a fixed frequency ratio between them so that every N cycles they bill on the same day. If it's an integer multiple then that's easy (just add the main package period that man times). Otherwise, check whether the next bill is when they're due to sync up, and if so, sync them. This uses a rather crude “they're within 15 days” heuristic.&lt;br /&gt;
&lt;br /&gt;
Otherwise, just call add_freq() using this package's frequency. Note that if the package is being prorated, $sdate will already have been moved to a prorate cycle date so that adding one month yields the right result.&lt;br /&gt;
&lt;br /&gt;
     if ( $increment_next_bill ) {&lt;br /&gt;
&lt;br /&gt;
       my $next_bill;&lt;br /&gt;
&lt;br /&gt;
       if ( my $main_pkg = $cust_pkg-&amp;gt;main_pkg ) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# supplemental package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# to keep in sync with the main package, simulate billing at &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# its frequency&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         my $main_pkg_freq = $main_pkg-&amp;gt;part_pkg-&amp;gt;freq;&lt;br /&gt;
         my $supp_pkg_freq = $part_pkg-&amp;gt;freq;&lt;br /&gt;
         if ( $supp_pkg_freq == 0 or $main_pkg_freq == 0 ) {&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# the UI should prevent setting up packages like this, but just&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# in case&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           return &amp;quot;unable to calculate supplemental package period ratio&amp;quot;;&lt;br /&gt;
         }&lt;br /&gt;
         my $ratio = $supp_pkg_freq / $main_pkg_freq;&lt;br /&gt;
         if ( $ratio == int($ratio) ) {&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# simple case: main package is X months, supp package is X*A months,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# advance supp package to where the main package will be in A cycles.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           $next_bill = $sdate;&lt;br /&gt;
           for (1..$ratio) {&lt;br /&gt;
             $next_bill = $part_pkg-&amp;gt;add_freq( $next_bill, $main_pkg_freq );&lt;br /&gt;
           }&lt;br /&gt;
         } else {&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# harder case: main package is X months, supp package is Y months.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# advance supp package by Y months. then if they're within half a &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# month of each other, resync them. this may result in the period&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# not being exactly Y months.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           $next_bill = $part_pkg-&amp;gt;add_freq( $sdate, $supp_pkg_freq );&lt;br /&gt;
           my $main_next_bill = $main_pkg-&amp;gt;bill;&lt;br /&gt;
           if ( $main_pkg-&amp;gt;bill &amp;lt;= $time ) {&lt;br /&gt;
             &amp;lt;nowiki&amp;gt;# then the main package has not yet been billed on this cycle;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
             &amp;lt;nowiki&amp;gt;# predict what its bill date will be.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
             $main_next_bill =&lt;br /&gt;
               $part_pkg-&amp;gt;add_freq( $main_next_bill, $main_pkg_freq );&lt;br /&gt;
           }&lt;br /&gt;
           if ( abs($main_next_bill - $next_bill) &amp;lt; 86400*15 ) {&lt;br /&gt;
             $next_bill = $main_next_bill;&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
       } else {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# the normal case, not a supplemental package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $next_bill = $part_pkg-&amp;gt;add_freq($sdate, $options{freq_override} || 0);&lt;br /&gt;
       return &amp;quot;unparsable frequency: &amp;quot;.&lt;br /&gt;
         ($options{freq_override} || $part_pkg-&amp;gt;freq)&lt;br /&gt;
         if $next_bill == -1;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
Set the “last bill” date to either the previous value of “next bill” or the setup date.&lt;br /&gt;
&lt;br /&gt;
Also, if there's any second-pass setup fee, apply that to the line item.&lt;br /&gt;
&lt;br /&gt;
'discount_left_setup' is a register used by the discount code for any flat-amount discount that's available for applying to the setup fee. Apply it here. This probably should set up a cust_bill_pkg_discount record but it's such an obscure case that we haven't needed it yet.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#pro-rating magic - if $recur_prog fiddled $sdate, want to use that&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# only for figuring next bill date, nothing else, so, reset $sdate again&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# here&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $sdate = $cust_pkg-&amp;gt;bill || $cust_pkg-&amp;gt;setup || $time;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#no need, its in $hash{last_bill}# my $last_bill = $cust_pkg-&amp;gt;last_bill;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $cust_pkg-&amp;gt;last_bill($sdate);&lt;br /&gt;
&lt;br /&gt;
       $cust_pkg-&amp;gt;setfield('bill', $next_bill );&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     if ( $param{'setup_fee'} ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# Add an additional setup fee at the billing stage.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# Used for prorate_defer_bill.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $setup += $param{'setup_fee'};&lt;br /&gt;
       $unitsetup = $cust_pkg-&amp;gt;base_setup();&lt;br /&gt;
       $lineitems++;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     if ( defined $param{'discount_left_setup'} ) {&lt;br /&gt;
         foreach my $discount_setup ( values %{$param{'discount_left_setup'}} ) {&lt;br /&gt;
             $setup -= $discount_setup;&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
   } # end of recurring fee&lt;br /&gt;
&lt;br /&gt;
   warn &amp;quot;\$setup is undefined&amp;quot; unless defined($setup);&lt;br /&gt;
   warn &amp;quot;\$recur is undefined&amp;quot; unless defined($recur);&lt;br /&gt;
   warn &amp;quot;\$cust_pkg-&amp;gt;bill is undefined&amp;quot; unless defined($cust_pkg-&amp;gt;bill);&lt;br /&gt;
&lt;br /&gt;
== Construct a cust_bill_pkg record ==&lt;br /&gt;
If $lineitems is zero then billing was entirely skipped and there's nothing to do.&lt;br /&gt;
&lt;br /&gt;
Otherwise, save the cust_pkg record with the new bill and last_bill dates. Only do that when processing the base package; add-on packages don't increment the bill date.&lt;br /&gt;
&lt;br /&gt;
Clean up $setup and $recur. The 'allow_negative_charges' config is no longer used.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# If there's line items, create em cust_bill_pkg records&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# If $cust_pkg has been modified, update it (if we're a real pkgpart)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   if ( $lineitems ) {&lt;br /&gt;
&lt;br /&gt;
     if ( $cust_pkg-&amp;gt;modified &amp;amp;&amp;amp; $cust_pkg-&amp;gt;pkgpart == $real_pkgpart ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# hmm.. and if just the options are modified in some weird price plan?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
       warn &amp;quot;  package &amp;quot;. $cust_pkg-&amp;gt;pkgnum. &amp;quot; modified; updating\n&amp;quot;&lt;br /&gt;
         if $DEBUG &amp;gt;1;&lt;br /&gt;
&lt;br /&gt;
       my $error = $cust_pkg-&amp;gt;replace( $old_cust_pkg,&lt;br /&gt;
                                       'depend_jobnum'=&amp;gt;$options{depend_jobnum},&lt;br /&gt;
                                       'options' =&amp;gt; { $cust_pkg-&amp;gt;options },&lt;br /&gt;
                                     )&lt;br /&gt;
         unless $options{no_commit};&lt;br /&gt;
       return &amp;quot;Error modifying pkgnum &amp;quot;. $cust_pkg-&amp;gt;pkgnum. &amp;quot;: $error&amp;quot;&lt;br /&gt;
         if $error; #just in case&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $setup = sprintf( &amp;quot;%.2f&amp;quot;, $setup );&lt;br /&gt;
     $recur = sprintf( &amp;quot;%.2f&amp;quot;, $recur );&lt;br /&gt;
     if ( $setup &amp;lt; 0 &amp;amp;&amp;amp; ! $conf-&amp;gt;exists('allow_negative_charges') ) {&lt;br /&gt;
       return &amp;quot;negative setup $setup for pkgnum &amp;quot;. $cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
     }&lt;br /&gt;
     if ( $recur &amp;lt; 0 &amp;amp;&amp;amp; ! $conf-&amp;gt;exists('allow_negative_charges') ) {&lt;br /&gt;
       return &amp;quot;negative recur $recur for pkgnum &amp;quot;. $cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
One more check. If the setup and recurring fees are both zero, we might skip creating a line item. It depends. Create a zero-dollar line item if:&lt;br /&gt;
&lt;br /&gt;
* The setup or recur is zero ''because it was discounted to zero'', and the '''discount-show-always''' config is on. The invoice will then show the applied discount.&lt;br /&gt;
* This is the main package in a ''bundle''. Its add-on packages will add hidden line items for the charges, but we need this one so that the bundle is shown with the correct package name.&lt;br /&gt;
* Either the cust_pkg or the part_pkg has the 'setup_show_zero' or 'recur_show_zero' flag. (It's not clear to me why these are separate flags, since they have exactly the same effect.)&lt;br /&gt;
&lt;br /&gt;
     my $discount_show_always = $conf-&amp;gt;exists('discount-show-always')&lt;br /&gt;
                                &amp;amp;&amp;amp; (    ($setup == 0 &amp;amp;&amp;amp; scalar(@setup_discounts))&lt;br /&gt;
                                     || ($recur == 0 &amp;amp;&amp;amp; scalar(@recur_discounts))&lt;br /&gt;
                                   );&lt;br /&gt;
&lt;br /&gt;
     if (    $setup != 0&lt;br /&gt;
          || $recur != 0&lt;br /&gt;
          || (!$part_pkg-&amp;gt;hidden &amp;amp;&amp;amp; $options{has_hidden}) #include some $0 lines&lt;br /&gt;
          || $discount_show_always&lt;br /&gt;
          || ($setup == 0 &amp;amp;&amp;amp; $cust_pkg-&amp;gt;_X_show_zero('setup'))&lt;br /&gt;
          || ($recur == 0 &amp;amp;&amp;amp; $cust_pkg-&amp;gt;_X_show_zero('recur'))&lt;br /&gt;
        )&lt;br /&gt;
     {&lt;br /&gt;
&lt;br /&gt;
       warn &amp;quot;    charges (setup=$setup, recur=$recur); adding line items\n&amp;quot;&lt;br /&gt;
         if $DEBUG &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
Find any package details that are set to display as “invoice details” and add them to the list.&lt;br /&gt;
&lt;br /&gt;
Then actually create the cust_bill_pkg. Huzzah!&lt;br /&gt;
&lt;br /&gt;
       my @cust_pkg_detail = map { $_-&amp;gt;detail } $cust_pkg-&amp;gt;cust_pkg_detail('I');&lt;br /&gt;
       if ( $DEBUG &amp;gt; 1 ) {&lt;br /&gt;
         warn &amp;quot;      adding customer package invoice detail: $_\n&amp;quot;&lt;br /&gt;
           foreach @cust_pkg_detail;&lt;br /&gt;
       }&lt;br /&gt;
       push @details, @cust_pkg_detail;&lt;br /&gt;
       my $cust_bill_pkg = new FS::cust_bill_pkg {&lt;br /&gt;
         'pkgnum'                &amp;lt;nowiki&amp;gt;=&amp;gt; $cust_pkg-&amp;gt;pkgnum,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'setup'                 &amp;lt;nowiki&amp;gt;=&amp;gt; $setup,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'unitsetup'             &amp;lt;nowiki&amp;gt;=&amp;gt; sprintf('%.2f', $unitsetup),&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'setup_billed_currency' =&amp;gt; $setup_billed_currency,&lt;br /&gt;
         'setup_billed_amount'   &amp;lt;nowiki&amp;gt;=&amp;gt; $setup_billed_amount,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'recur'                 &amp;lt;nowiki&amp;gt;=&amp;gt; $recur,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'unitrecur'             &amp;lt;nowiki&amp;gt;=&amp;gt; sprintf('%.2f', $unitrecur),&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'recur_billed_currency' =&amp;gt; $recur_billed_currency,&lt;br /&gt;
         'recur_billed_amount'   &amp;lt;nowiki&amp;gt;=&amp;gt; $recur_billed_amount,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'quantity'              &amp;lt;nowiki&amp;gt;=&amp;gt; $override_quantity || $cust_pkg-&amp;gt;quantity,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'details'               &amp;lt;nowiki&amp;gt;=&amp;gt; \@details,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'discounts'             &amp;lt;nowiki&amp;gt;=&amp;gt; [ @setup_discounts, @recur_discounts ],&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'hidden'                &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg-&amp;gt;hidden,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'freq'                  &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg-&amp;gt;freq,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       };&lt;br /&gt;
&lt;br /&gt;
Determine the start and end of the billing period. Usually it's “last bill to current bill”, or “current bill to next bill”, but there are a couple of special cases. Also record which part_pkg this line item was generated for, if it's an add-on package.&lt;br /&gt;
&lt;br /&gt;
       if ( $part_pkg-&amp;gt;option('prorate_defer_bill',1)&lt;br /&gt;
            and !$hash{last_bill} ) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# both preceding and upcoming, technically&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;sdate( $cust_pkg-&amp;gt;setup );&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $cust_pkg-&amp;gt;bill );&lt;br /&gt;
       } elsif ( $part_pkg-&amp;gt;recur_temporality eq 'preceding' ) {&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;sdate( $hash{last_bill} );&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $sdate - 86399   ); #60s*60m*24h-1&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $time ) if $options{cancel};&lt;br /&gt;
       } else { #if ( $part_pkg-&amp;gt;recur_temporality eq 'upcoming' )&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;sdate( $sdate );&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $cust_pkg-&amp;gt;bill );&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;#$cust_bill_pkg-&amp;gt;edate( $time ) if $options{cancel};&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
       $cust_bill_pkg-&amp;gt;pkgpart_override($part_pkg-&amp;gt;pkgpart)&lt;br /&gt;
         unless $part_pkg-&amp;gt;pkgpart == $real_pkgpart;&lt;br /&gt;
&lt;br /&gt;
Record the results in the right places: add setup and recur to totals, give the cust_bill_pkg to the tax calculator, and add it to the line item array.&lt;br /&gt;
&lt;br /&gt;
       $$total_setup += $setup;&lt;br /&gt;
       $$total_recur += $recur;&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# handle taxes&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
       my $error = $tax_engine-&amp;gt;add_sale($cust_bill_pkg);&lt;br /&gt;
       return $error if $error;&lt;br /&gt;
&lt;br /&gt;
       $cust_bill_pkg-&amp;gt;set_display(&lt;br /&gt;
         part_pkg     &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         real_pkgpart =&amp;gt; $real_pkgpart,&lt;br /&gt;
       );&lt;br /&gt;
&lt;br /&gt;
       push @$cust_bill_pkgs, $cust_bill_pkg;&lt;br /&gt;
&lt;br /&gt;
     } #if $setup != 0 || $recur != 0&lt;br /&gt;
&lt;br /&gt;
   } #if $line_items&lt;br /&gt;
&lt;br /&gt;
   '';&lt;br /&gt;
&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
And we're done!&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Developer:Billing_Internals&amp;diff=9649</id>
		<title>Freeside:3:Documentation:Developer:Billing Internals</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Developer:Billing_Internals&amp;diff=9649"/>
				<updated>2017-02-08T05:41:04Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;= FS::cust_main::Billing::bill = This is the main method for generating invoices. It's called on a single FS::cust_main, finds any packages that are due to be billed, and crea...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= FS::cust_main::Billing::bill =&lt;br /&gt;
This is the main method for generating invoices. It's called on a single FS::cust_main, finds any packages that are due to be billed, and creates one or more invoices with the package charges.&lt;br /&gt;
&lt;br /&gt;
It does not handle sending the invoice to the customer, charging their credit card, suspending packages if they're overdue, or anything else of that kind. Those are events run from collect(). See below.&lt;br /&gt;
&lt;br /&gt;
== Initial setup ==&lt;br /&gt;
Grab the customer and options. Immediately exit if the customer is “complimentary”. Set the debug level, create a log context, and write a log entry.&lt;br /&gt;
&lt;br /&gt;
   my( $self, %options ) = @_;&lt;br /&gt;
&lt;br /&gt;
   return '' if $self-&amp;gt;complimentary eq 'Y';&lt;br /&gt;
&lt;br /&gt;
   local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG &amp;gt; $DEBUG;&lt;br /&gt;
   my $log = FS::Log-&amp;gt;new('FS::cust_main::Billing::bill');&lt;br /&gt;
   my %logopt = (object =&amp;gt; $self);&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('start', %logopt);&lt;br /&gt;
   warn &amp;quot;$me bill customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Set the billing clock. $time is the date we are “billing on”. For example, freeside-daily -y (bill packages a few days in the future so that invoices can be mailed out early) sets $time. This is almost, but not exactly, the same as changing the system clock.&lt;br /&gt;
&lt;br /&gt;
$invoice_time is the date that will appear on invoices. Normally it's the same as $time; the -n option overrides that and forces it to be the real system time.&lt;br /&gt;
&lt;br /&gt;
$cmp_time is the “comparison time”: a package gets billed if its next-bill date is &amp;lt;= $cmp_time. This is the same as $time unless '''next-bill-ignore-time''' is set, in which case we use the end of the day. Note that this does ''not'' affect certain other important time values such as prorate cutoffs.&lt;br /&gt;
&lt;br /&gt;
Also parse the 'not_pkgpart' option to exclude specific packages from billing.&lt;br /&gt;
&lt;br /&gt;
   my $time = $options{'time'} || time;&lt;br /&gt;
   my $invoice_time = $options{'invoice_time'} || $time;&lt;br /&gt;
&lt;br /&gt;
   my $cmp_time = ( $conf-&amp;gt;exists('next-bill-ignore-time')&lt;br /&gt;
                      ? day_end( $time )&lt;br /&gt;
                      : $time&lt;br /&gt;
                  );&lt;br /&gt;
&lt;br /&gt;
   $options{'not_pkgpart'} ||= {};&lt;br /&gt;
   $options{'not_pkgpart'} = { map { $_ =&amp;gt; 1 }&lt;br /&gt;
                                   split(/\s*,\s*/, $options{'not_pkgpart'})&lt;br /&gt;
                             }&lt;br /&gt;
     unless ref($options{'not_pkgpart'});&lt;br /&gt;
&lt;br /&gt;
Block out interruptions, start a transaction, and lock the customer record (so that packages aren't ordered or status-changed, payments aren't recorded, etc. while doing this). If anything goes wrong during billing, we will rollback and return an error.&lt;br /&gt;
&lt;br /&gt;
   local $SIG{HUP} = 'IGNORE';&lt;br /&gt;
   local $SIG{INT} = 'IGNORE';&lt;br /&gt;
   local $SIG{QUIT} = 'IGNORE';&lt;br /&gt;
   local $SIG{TERM} = 'IGNORE';&lt;br /&gt;
   local $SIG{TSTP} = 'IGNORE';&lt;br /&gt;
   local $SIG{PIPE} = 'IGNORE';&lt;br /&gt;
&lt;br /&gt;
   my $oldAutoCommit = $FS::UID::AutoCommit;&lt;br /&gt;
   local $FS::UID::AutoCommit = 0;&lt;br /&gt;
   my $dbh = dbh;&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('acquiring lock', %logopt);&lt;br /&gt;
   warn &amp;quot;$me acquiring lock on customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
   $self-&amp;gt;select_for_update; #mutex&lt;br /&gt;
&lt;br /&gt;
== Run pre-billing events. ==&lt;br /&gt;
Certain billing event actions are always run just before billing, because they affect the charges that will appear on the bill. These are:&lt;br /&gt;
&lt;br /&gt;
* pkg_discount&lt;br /&gt;
* cust_pkg_fee, cust_fee, and pkg_fee&lt;br /&gt;
* The old “fee” event that created fees as one-time charges.&lt;br /&gt;
&lt;br /&gt;
In this section we call do_cust_event() with a 'stage' parameter to specify only the pre-billing events.&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('running pre-bill events', %logopt);&lt;br /&gt;
   warn &amp;quot;$me running pre-bill events for customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
   my $error = $self-&amp;gt;do_cust_event(&lt;br /&gt;
     'debug'      &amp;lt;nowiki&amp;gt;=&amp;gt; ( $options{'debug'} || 0 ),&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     'time'       &amp;lt;nowiki&amp;gt;=&amp;gt; $invoice_time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     'check_freq' =&amp;gt; $options{'check_freq'},&lt;br /&gt;
     'stage'      &amp;lt;nowiki&amp;gt;=&amp;gt; 'pre-bill',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   )&lt;br /&gt;
     unless $options{no_commit};&lt;br /&gt;
   if ( $error ) {&lt;br /&gt;
     $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
     return $error;&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   $log-&amp;gt;debug('done running pre-bill events', %logopt);&lt;br /&gt;
   warn &amp;quot;$me done running pre-bill events for customer &amp;quot;. $self-&amp;gt;custnum. &amp;quot;\n&amp;quot;&lt;br /&gt;
     if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
== Identify billable packages and add-on packages. ==&lt;br /&gt;
Set up space for the line items (%cust_bill_pkg), setup and recur totals, and a tax engine. Every time we add a line item to the invoice, we must push it to the line item array, add its setup and recur charges to the totals, and send it to the tax engine.&lt;br /&gt;
&lt;br /&gt;
A single call to bill() can generate multiple invoices. Packages can be eligible or ineligible for automatic payment; that's implemented by splitting non-auto-payment packages off onto a separate invoice. So we'll (potentially) make two invoices here, and need a line items array, subtotals, etc. for each of them. If any package has the “separate_bill” flag, we'll make an additional invoice just for that package.&lt;br /&gt;
&lt;br /&gt;
Also, if the set of packages to bill wasn't specified ('pkg_list'; it's usually not), then use all of the customer's non-canceled packages. We will check later for whether they're due for billing; that's complicated.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;#keep auto-charge and non-auto-charge line items separate&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   my @passes = ( '', 'no_auto' );&lt;br /&gt;
&lt;br /&gt;
   my %cust_bill_pkg = map { $_ =&amp;gt; [] } @passes;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# find the packages which are due for billing, find out how much they are&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;amp; generate invoice database.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my %total_setup   &amp;lt;nowiki&amp;gt;= map { my $z = 0; $_ =&amp;gt; \$z; } @passes;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   my %total_recur   &amp;lt;nowiki&amp;gt;= map { my $z = 0; $_ =&amp;gt; \$z; } @passes;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my @precommit_hooks = ();&lt;br /&gt;
&lt;br /&gt;
   $options{'pkg_list'} ||= [ $self-&amp;gt;ncancelled_pkgs ];  &amp;lt;nowiki&amp;gt;#param checks?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my %tax_engines;&lt;br /&gt;
   my $tax_is_batch = '';&lt;br /&gt;
   foreach (@passes) {&lt;br /&gt;
     $tax_engines{$_} = FS::TaxEngine-&amp;gt;new(cust_main    &amp;lt;nowiki&amp;gt;=&amp;gt; $self,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                           invoice_time =&amp;gt; $invoice_time,&lt;br /&gt;
                                           cancel       &amp;lt;nowiki&amp;gt;=&amp;gt; $options{cancel},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                           estimate     &amp;lt;nowiki&amp;gt;=&amp;gt; $options{estimate},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                          );&lt;br /&gt;
     $tax_is_batch ||= $tax_engines{$_}-&amp;gt;info-&amp;gt;{batch};&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
Here we start the main loop over all packages. Skip any that are excluded by 'not_pkgpart', or by 'no_prepaid'. (freeside-daily sets this, as prepaid packages are billed by another mechanism.) And if there's an explicit 'pkg_list' then use only those.&lt;br /&gt;
&lt;br /&gt;
   foreach my $cust_pkg ( @{ $options{'pkg_list'} } ) {&lt;br /&gt;
&lt;br /&gt;
     next if $options{'not_pkgpart'}-&amp;gt;{$cust_pkg-&amp;gt;pkgpart};&lt;br /&gt;
&lt;br /&gt;
     my $part_pkg = $cust_pkg-&amp;gt;part_pkg;&lt;br /&gt;
&lt;br /&gt;
     next if $options{'no_prepaid'} &amp;amp;&amp;amp; $part_pkg-&amp;gt;is_prepaid;&lt;br /&gt;
&lt;br /&gt;
     $log-&amp;gt;debug('bill package '. $cust_pkg-&amp;gt;pkgnum, %logopt);&lt;br /&gt;
     warn &amp;quot;  bill package &amp;quot;. $cust_pkg-&amp;gt;pkgnum. &amp;quot;\n&amp;quot; if $DEBUG;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#? to avoid use of uninitialized value errors... ?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $cust_pkg-&amp;gt;setfield('bill', '')&lt;br /&gt;
       unless defined($cust_pkg-&amp;gt;bill);&lt;br /&gt;
&lt;br /&gt;
Call self_and_bill_linked() to find any billing add-on packages. Add-ons are additional package definitions (part_pkg records, plus all their accessories) that are linked to a “main” package definition, and generate additional charges on the bill. For example, a phone package that has a monthly fee plus long-distance usage billing would have an add-on voip_cdr package to do the usage billing.&lt;br /&gt;
&lt;br /&gt;
Add-on packages always create separate cust_bill_pkg records, with the “pkgpart_override” field referencing the add-on package. Normally, these charges will show separately on the invoice, but they can be “bundled” with the main package by setting part_pkg_link.hidden. At this point we set a flag if this looks like a bundle package, as that will matter later.&lt;br /&gt;
&lt;br /&gt;
     my $real_pkgpart = $cust_pkg-&amp;gt;pkgpart;&lt;br /&gt;
     my %hash = $cust_pkg-&amp;gt;hash;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# we could implement this bit as FS::part_pkg::has_hidden, but we already&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# suffer from performance issues&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $options{has_hidden} = 0;&lt;br /&gt;
     my @part_pkg = $part_pkg-&amp;gt;self_and_bill_linked;&lt;br /&gt;
     $options{has_hidden} = 1 if ($part_pkg[1] &amp;amp;&amp;amp; $part_pkg[1]-&amp;gt;hidden);&lt;br /&gt;
&lt;br /&gt;
== Create line items for each billable package. ==&lt;br /&gt;
As we create line items we'll push them onto $cust_bill_pkg{$pass} (either '' or 'no_auto'). First, balance transfers: if this package is being billed for the first time after a package change, and package balances are enabled, then credit the old package to zero out its balance, and make a line item to carry the balance forward to its successor package.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# if this package was changed from another package,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# and it hasn't been billed since then,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# and package balances are enabled,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     if ( $cust_pkg-&amp;gt;change_pkgnum&lt;br /&gt;
         and $cust_pkg-&amp;gt;change_date &amp;gt;= ($cust_pkg-&amp;gt;last_bill || 0)&lt;br /&gt;
         and $cust_pkg-&amp;gt;change_date &amp;lt;  $invoice_time&lt;br /&gt;
       and $conf-&amp;gt;exists('pkg-balances') )&lt;br /&gt;
     {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# _transfer_balance will also create the appropriate credit&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my @transfer_items = $self-&amp;gt;_transfer_balance($cust_pkg);&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# $part_pkg[0] is the &amp;quot;real&amp;quot; part_pkg&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $pass = ($cust_pkg-&amp;gt;no_auto || $part_pkg[0]-&amp;gt;no_auto) ?&lt;br /&gt;
                   'no_auto' : '';&lt;br /&gt;
       push @{ $cust_bill_pkg{$pass} }, @transfer_items;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# treating this as recur, just because most charges are recur...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       ${$total_recur{$pass}} += $_-&amp;gt;recur foreach @transfer_items;&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# currently not considering separate_bill here, as it's for &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# one-time charges only&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Loop over the part_pkgs (first the “real” package definition, then the add-ons) and decide which invoice to put the charges on. Normally, $pass = '', which will put all charges on a single invoice, but no_auto packages have to go on another invoice, and each package with separate_bill needs yet another (so it will create entries in %total_setup, %total_recur, %cust_bill_pkg, etc.).&lt;br /&gt;
&lt;br /&gt;
     foreach my $part_pkg ( @part_pkg ) {&lt;br /&gt;
&lt;br /&gt;
       my $this_cust_pkg = $cust_pkg;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# for add-on packages, copy the object to avoid leaking changes back to&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# the caller if pkg_list is in use; see RT#73607&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       if ( $part_pkg-&amp;gt;get('pkgpart') != $real_pkgpart ) {&lt;br /&gt;
         $this_cust_pkg = FS::cust_pkg-&amp;gt;new({ %hash });&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
       my $pass = '';&lt;br /&gt;
       if ( $this_cust_pkg-&amp;gt;separate_bill ) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# if no_auto is also set, that's fine. we just need to not have&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# invoices that are both auto and no_auto, and since the package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# gets an invoice all to itself, it will only be one or the other.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $pass = $this_cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
         if (!exists $cust_bill_pkg{$pass}) { # it may not exist yet&lt;br /&gt;
           push @passes, $pass;&lt;br /&gt;
           $total_setup{$pass} = do { my $z = 0; \$z };&lt;br /&gt;
           $total_recur{$pass} = do { my $z = 0; \$z };&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# it also needs its own tax context&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           $tax_engines{$pass} = FS::TaxEngine-&amp;gt;new(&lt;br /&gt;
                                   cust_main    &amp;lt;nowiki&amp;gt;=&amp;gt; $self,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                   invoice_time =&amp;gt; $invoice_time,&lt;br /&gt;
                                   cancel       &amp;lt;nowiki&amp;gt;=&amp;gt; $options{cancel},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                   estimate     &amp;lt;nowiki&amp;gt;=&amp;gt; $options{estimate},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 );&lt;br /&gt;
           $cust_bill_pkg{$pass} = [];&lt;br /&gt;
         }&lt;br /&gt;
       } elsif ( ($this_cust_pkg-&amp;gt;no_auto || $part_pkg-&amp;gt;no_auto) ) {&lt;br /&gt;
         $pass = 'no_auto';&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
The loop here checks whether the package is due for billing, and handles back-billing of multiple cycles. For example, if a monthly package's bill date is more than a month in the past, we'll bill as many months as necessary to bring the package up to date. The loop normally exits once the package bill date is in the future, but will exit after a single pass if we're billing on cancellation, or if freeside-daily -o is in use. It will also exit if there's an error or if the bill date isn't being incremented (as for a one-time charge).&lt;br /&gt;
&lt;br /&gt;
_make_lines() is complex enough to have [#_make_lines its own section of the docs], but it generates a line item and does the following with it:&lt;br /&gt;
&lt;br /&gt;
* appends it to the arrayref in 'line_items' (so, to $cust_bill_pkg{$pass}).&lt;br /&gt;
* adds its setup and recur fees to the running totals&lt;br /&gt;
* informs the tax engine about it (via the add_sale() method)&lt;br /&gt;
* increments the package's last-bill and next-bill dates&lt;br /&gt;
* returns an error if anything goes wrong&lt;br /&gt;
&lt;br /&gt;
       my $next_bill = $this_cust_pkg-&amp;gt;getfield('bill') || 0;&lt;br /&gt;
       my $error;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# let this run once if this is the last bill upon cancellation&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       while ( $next_bill &amp;lt;= $cmp_time or $options{cancel} ) {&lt;br /&gt;
         $error =&lt;br /&gt;
           $self-&amp;gt;_make_lines( 'part_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'cust_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $this_cust_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'precommit_hooks'     &amp;lt;nowiki&amp;gt;=&amp;gt; \@precommit_hooks,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'line_items'          &amp;lt;nowiki&amp;gt;=&amp;gt; $cust_bill_pkg{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'setup'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_setup{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'recur'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_recur{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'tax_engine'          &amp;lt;nowiki&amp;gt;=&amp;gt; $tax_engines{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'time'                &amp;lt;nowiki&amp;gt;=&amp;gt; $time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'real_pkgpart'        &amp;lt;nowiki&amp;gt;=&amp;gt; $real_pkgpart,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               'options'             &amp;lt;nowiki&amp;gt;=&amp;gt; \%options,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                             );&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# Stop if anything goes wrong&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if $error;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# or if we're not incrementing the bill date.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if ($this_cust_pkg-&amp;gt;getfield('bill') || 0) == $next_bill;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# or if we're letting it run only once&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if $options{cancel};&lt;br /&gt;
&lt;br /&gt;
         $next_bill = $this_cust_pkg-&amp;gt;getfield('bill') || 0;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;#stop if -o was passed to freeside-daily&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         last if $options{'one_recur'};&lt;br /&gt;
       }&lt;br /&gt;
       if ($error) {&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return $error;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     } #foreach my $part_pkg&lt;br /&gt;
&lt;br /&gt;
   } #foreach my $cust_pkg&lt;br /&gt;
&lt;br /&gt;
== Identify applicable fees and tax adjustments and create line items. ==&lt;br /&gt;
At this point we have a list of package line items for each invoice the customer might receive. Now loop through the (possible) invoices and process non-package fees.&lt;br /&gt;
&lt;br /&gt;
A “fee origin” (FS::FeeOrigin_Mixin) is an instruction or “order” to charge a fee&amp;lt;nowiki&amp;gt;; the classes are FS::cust_event_fee (fees generated by billing events, &amp;lt;/nowiki&amp;gt;such as late fees) and FS::cust_pkg_reason_fee (unsuspension fees). When the fee is put on an invoice, the order is fulfilled; the billpkgnum of the fee line item is then recorded on the fee origin record.&lt;br /&gt;
&lt;br /&gt;
The by_cust() method does a search for all fee origins for a customer; we limit it to records with billpkgnum = NULL to get fees that still need to be charged. Those are @pending_fees.&lt;br /&gt;
&lt;br /&gt;
We process fees at this stage (after generating all package line items) because we need to know if the customer is going to receive an invoice or not. Fees can be defined (via the 'nextbill' flag) to be charged immediately, or on the customer's next bill. If all pending fees have 'nextbill' and there aren't any package charges on this invoice (@cust_bill_pkg is empty) then skip to the next invoice. If the customer has no package charges at all, then the 'nextbill' fees won't be charged at all on this billing date and will remain pending.&lt;br /&gt;
&lt;br /&gt;
   foreach my $pass (@passes) { # keys %cust_bill_pkg )&lt;br /&gt;
&lt;br /&gt;
     my @cust_bill_pkg = _omit_zero_value_bundles(@{ $cust_bill_pkg{$pass} });&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;$me billing pass $pass\n&amp;quot;&lt;br /&gt;
            &amp;lt;nowiki&amp;gt;#.Dumper(\@cust_bill_pkg).&amp;quot;\n&amp;quot;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       if $DEBUG &amp;gt; 2;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# process fees&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
     my @pending_fees = FS::FeeOrigin_Mixin-&amp;gt;by_cust($self-&amp;gt;custnum,&lt;br /&gt;
       hashref =&amp;gt; { 'billpkgnum' =&amp;gt; '' }&lt;br /&gt;
     );&lt;br /&gt;
     warn &amp;quot;$me found pending fees:\n&amp;quot;.Dumper(\@pending_fees).&amp;quot;\n&amp;quot;&lt;br /&gt;
       if @pending_fees and $DEBUG &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# determine whether to generate an invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     my $generate_bill = scalar(@cust_bill_pkg) &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
     foreach my $fee (@pending_fees) {&lt;br /&gt;
       $generate_bill = 1 unless $fee-&amp;gt;nextbill;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# don't create an invoice with no line items, or where the only line &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# items are fees that are supposed to be held until the next invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     next if !$generate_bill;&lt;br /&gt;
&lt;br /&gt;
For each fee origin, do some things. Skip the fee if the fee definition is somehow inappropriate:&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# calculate fees...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     my @fee_items;&lt;br /&gt;
     foreach my $fee_origin (@pending_fees) {&lt;br /&gt;
       my $part_fee = $fee_origin-&amp;gt;part_fee;&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# check whether the fee is applicable before doing anything expensive:&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# if the fee def belongs to a different agent, don't charge the fee.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# event conditions should prevent this, but just in case they don't,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# skip the fee.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       if ( $part_fee-&amp;gt;agentnum and $part_fee-&amp;gt;agentnum != $self-&amp;gt;agentnum ) {&lt;br /&gt;
         warn &amp;quot;tried to charge fee#&amp;quot;.$part_fee-&amp;gt;feepart .&lt;br /&gt;
              &amp;quot; on customer#&amp;quot;.$self-&amp;gt;custnum.&amp;quot; from a different agent.\n&amp;quot;;&lt;br /&gt;
         next;&lt;br /&gt;
       }&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# also skip if it's disabled&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       next if $part_fee-&amp;gt;disabled eq 'Y';&lt;br /&gt;
&lt;br /&gt;
Then determine the “basis” for the fee, in case it's defined as a percentage of something. The fee origin may declare that the fee applies to a previous invoice (such as a finance charge). Otherwise, the fee applies to the ''current'' invoice. Since the current invoice doesn't exist yet, construct one temporarily that has the line items of the current invoice.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# Decide which invoice to base the fee on.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $cust_bill = $fee_origin-&amp;gt;cust_bill;&lt;br /&gt;
       if (!$cust_bill) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# Then link it to the current invoice. This isn't the real cust_bill&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# object that will be inserted--in particular there are no taxes yet.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# If you want to charge a fee on the total invoice amount including&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# taxes, you have to put the fee on the next invoice.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $cust_bill = FS::cust_bill-&amp;gt;new({&lt;br /&gt;
             'custnum'       &amp;lt;nowiki&amp;gt;=&amp;gt; $s&amp;lt;/nowiki&amp;gt;elf-&amp;gt;custnum,&lt;br /&gt;
             'cust_bill_pkg' =&amp;gt; \@cust_bill_pkg,&lt;br /&gt;
             'charged'       &amp;lt;nowiki&amp;gt;=&amp;gt; ${ $total_setup{$pass} } +&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                ${ $total_recur{$pass} },&lt;br /&gt;
         });&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# If the origin is for a specific package, then only apply the fee to&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# line items from that package.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         if ( my $cust_pkg = $fee_origin-&amp;gt;cust_pkg ) {&lt;br /&gt;
           my @charge_fee_on_item;&lt;br /&gt;
           my $charge_fee_on_amount = 0;&lt;br /&gt;
           foreach (@cust_bill_pkg) {&lt;br /&gt;
             if ($_-&amp;gt;pkgnum == $cust_pkg-&amp;gt;pkgnum) {&lt;br /&gt;
               push @charge_fee_on_item, $_;&lt;br /&gt;
               $charge_fee_on_amount += $_-&amp;gt;setup + $_-&amp;gt;recur;&lt;br /&gt;
             }&lt;br /&gt;
           }&lt;br /&gt;
           $cust_bill-&amp;gt;set('cust_bill_pkg', \@charge_fee_on_item);&lt;br /&gt;
           $cust_bill-&amp;gt;set('charged', $charge_fee_on_amount);&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
       } # $cust_bill is now set&lt;br /&gt;
&lt;br /&gt;
FS::part_fee-&amp;gt;lineitem() asks the fee definition to make a cust_bill_pkg record for this fee, as applied to a specific invoice. After the invoice is inserted, we're going to need to record the billpkgnum on the fee origin record. FS::cust_bill_pkg-&amp;gt;insert knows to do this.&lt;br /&gt;
&lt;br /&gt;
Then append any fee line items to the invoice, add them to the running totals, and send them to the tax engine.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# calculate the fee&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $fee_item = $part_fee-&amp;gt;lineitem($cust_bill) or next;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# link this so that we can clear the marker on inserting the line item&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $fee_item-&amp;gt;set('fee_origin', $fee_origin);&lt;br /&gt;
       push @fee_items, $fee_item;&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# add fees to the invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     foreach my $fee_item (@fee_items) {&lt;br /&gt;
&lt;br /&gt;
       push @cust_bill_pkg, $fee_item;&lt;br /&gt;
       ${ $total_setup{$pass} } += $fee_item-&amp;gt;setup;&lt;br /&gt;
       ${ $total_recur{$pass} } += $fee_item-&amp;gt;recur;&lt;br /&gt;
&lt;br /&gt;
       my $part_fee = $fee_item-&amp;gt;part_fee;&lt;br /&gt;
       my $fee_location = $self-&amp;gt;ship_location; # I think?&lt;br /&gt;
&lt;br /&gt;
       my $error = $tax_engines{''}-&amp;gt;add_sale($fee_item);&lt;br /&gt;
&lt;br /&gt;
       return $error if $error;&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Add the special “postal invoice fee”. This was a per-invoice flat fee for sending customers a printed invoice; like all “old” fees it worked by adding a one-time charge. charge_postal_fee() is the method to do that. It adds a one-time package to the customer ''in the middle of billing'', after all their other packages have been billed, for exactly the same reason that other fees are processed at this point in billing. This feature is obsolete so I won't say any more.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# XXX implementation of fees is supposed to make this go away...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     if ( scalar( grep { $_-&amp;gt;recur &amp;amp;&amp;amp; $_-&amp;gt;recur &amp;gt; 0 } @cust_bill_pkg) ||&lt;br /&gt;
            !$conf-&amp;gt;exists('postal_invoice-recurring_only')&lt;br /&gt;
        )&lt;br /&gt;
     {&lt;br /&gt;
&lt;br /&gt;
       my $postal_pkg = $self-&amp;gt;charge_postal_fee();&lt;br /&gt;
       if ( $postal_pkg &amp;amp;&amp;amp; !ref( $postal_pkg ) ) {&lt;br /&gt;
&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return &amp;quot;can't charge postal invoice fee for customer &amp;quot;.&lt;br /&gt;
           $self-&amp;gt;custnum. &amp;quot;: $postal_pkg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
       } elsif ( $postal_pkg ) {&lt;br /&gt;
&lt;br /&gt;
         my $real_pkgpart = $postal_pkg-&amp;gt;pkgpart;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# we could implement this bit as FS::part_pkg::has_hidden, but we already&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# suffer from performance issues&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $options{has_hidden} = 0;&lt;br /&gt;
         my @part_pkg = $postal_pkg-&amp;gt;part_pkg-&amp;gt;self_and_bill_linked;&lt;br /&gt;
         $options{has_hidden} = 1 if ($part_pkg[1] &amp;amp;&amp;amp; $part_pkg[1]-&amp;gt;hidden);&lt;br /&gt;
&lt;br /&gt;
         foreach my $part_pkg ( @part_pkg ) {&lt;br /&gt;
           my %postal_options = %options;&lt;br /&gt;
           delete $postal_options{cancel};&lt;br /&gt;
           my $error =&lt;br /&gt;
             $self-&amp;gt;_make_lines( 'part_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'cust_pkg'            &amp;lt;nowiki&amp;gt;=&amp;gt; $postal_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'precommit_hooks'     &amp;lt;nowiki&amp;gt;=&amp;gt; \@precommit_hooks,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'line_items'          &amp;lt;nowiki&amp;gt;=&amp;gt; \@cust_bill_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'setup'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_setup{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'recur'               &amp;lt;nowiki&amp;gt;=&amp;gt; $total_recur{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'tax_engine'          &amp;lt;nowiki&amp;gt;=&amp;gt; $tax_engines{$pass},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'time'                &amp;lt;nowiki&amp;gt;=&amp;gt; $time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'real_pkgpart'        &amp;lt;nowiki&amp;gt;=&amp;gt; $real_pkgpart,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                 'options'             &amp;lt;nowiki&amp;gt;=&amp;gt; \%postal_options,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                               );&lt;br /&gt;
           if ($error) {&lt;br /&gt;
             $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
             return $error;&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# it's silly to have a zero value postal_pkg, but....&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         @cust_bill_pkg = _omit_zero_value_bundles(@cust_bill_pkg);&lt;br /&gt;
&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Tax adjustments (FS::cust_tax_adjustment) are manual adjustment records that work much like fee origins, but are created manually. If a tax adjustment exists for the customer, and doesn't have a billpkgnum yet, and an invoice is being generated, then create a line item with the amount and itemdesc specified in the tax adjustment. Note that this line item ''is a tax line item'': it has pkgnum = 0, feepart = NULL, and recur = 0. On that line item, set 'cust_tax_adjustment' to the tax adjustment record so that cust_bill_pkg-&amp;gt;insert can record that the adjustment was billed.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#add tax adjustments&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#XXX does this work with batch tax engines?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     warn &amp;quot;adding tax adjustments...\n&amp;quot; if $DEBUG &amp;gt; 2;&lt;br /&gt;
     foreach my $cust_tax_adjustment (&lt;br /&gt;
       qsearch('cust_tax_adjustment', { 'custnum'    &amp;lt;nowiki&amp;gt;=&amp;gt; $self-&amp;gt;custnum,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                        'billpkgnum' =&amp;gt; '',&lt;br /&gt;
                                      }&lt;br /&gt;
              )&lt;br /&gt;
     ) {&lt;br /&gt;
&lt;br /&gt;
       my $tax = sprintf('%.2f', $cust_tax_adjustment-&amp;gt;amount );&lt;br /&gt;
&lt;br /&gt;
       my $itemdesc = $cust_tax_adjustment-&amp;gt;taxname;&lt;br /&gt;
       $itemdesc = '' if $itemdesc eq 'Tax';&lt;br /&gt;
&lt;br /&gt;
       push @cust_bill_pkg, new FS::cust_bill_pkg {&lt;br /&gt;
         'pkgnum'      &amp;lt;nowiki&amp;gt;=&amp;gt; 0,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'setup'       &amp;lt;nowiki&amp;gt;=&amp;gt; $tax,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'recur'       &amp;lt;nowiki&amp;gt;=&amp;gt; 0,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'sdate'       &amp;lt;nowiki&amp;gt;=&amp;gt; '',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'edate'       &amp;lt;nowiki&amp;gt;=&amp;gt; '',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'itemdesc'    &amp;lt;nowiki&amp;gt;=&amp;gt; $itemdesc,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'itemcomment' =&amp;gt; $cust_tax_adjustment-&amp;gt;comment,&lt;br /&gt;
         'cust_tax_adjustment' =&amp;gt; $cust_tax_adjustment,&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;#'cust_bill_pkg_tax_location' =&amp;gt; \@cust_bill_pkg_tax_location,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       };&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
== Create the invoice record. ==&lt;br /&gt;
Before storing the record, calculate some totals:&lt;br /&gt;
&lt;br /&gt;
* cust_bill.charged: the sum of charges on this invoice (taxes will be added later)&lt;br /&gt;
* billing_balance: the customer's balance before this invoice.&lt;br /&gt;
* previous_balance: the customer's balance immediately after their previous invoice, determined as (billing_balance + charged) on that invoice.&lt;br /&gt;
&lt;br /&gt;
Note that ''billing_balance and previous_balance are obsolete''. They were historically used to create invoice summary sections (“Balance on previous invoice”, “Payments/credits since then”), but since 2014 we actually look at the transaction history. This method produces correct results if transactions get voided.&lt;br /&gt;
&lt;br /&gt;
     my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } );&lt;br /&gt;
&lt;br /&gt;
     my $balance = $self-&amp;gt;balance;&lt;br /&gt;
&lt;br /&gt;
     my $previous_bill = qsearchs({ 'table'     &amp;lt;nowiki&amp;gt;=&amp;gt; 'cust_bill',&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                    'hashref'   &amp;lt;nowiki&amp;gt;=&amp;gt; { custnum=&amp;gt;$self-&amp;gt;custnum },&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                                    'extra_sql' =&amp;gt; 'ORDER BY _date DESC LIMIT 1',&lt;br /&gt;
                                 });&lt;br /&gt;
     my $previous_balance =&lt;br /&gt;
       $previous_bill&lt;br /&gt;
         ? ( $previous_bill-&amp;gt;billing_balance + $previous_bill-&amp;gt;charged )&lt;br /&gt;
         : 0;&lt;br /&gt;
&lt;br /&gt;
Now store the record. FS::cust_bill::insert() will notice the arrayref of line items and insert all of them. Line items with 'fee_origin' or 'tax_adjustment' references will also trigger updates to those records.&lt;br /&gt;
&lt;br /&gt;
The 'no_commit' option is for simulating billing, and will stop the record from being inserted. Currently this is used only with term prepayment discounts (which are no longer a supported feature) to figure out the “amount saved” over the prepaid term.&lt;br /&gt;
&lt;br /&gt;
Note that (like almost anywhere else in Freeside) $FS::UID::AutoCommit = 0 will also prevent bill() from committing its changes. This is how quotations work: FS::quotation-&amp;gt;estimate opens a transaction, inserts packages, runs bill(), and then reverts the whole transaction.&lt;br /&gt;
&lt;br /&gt;
     $log-&amp;gt;debug('creating the new invoice', %logopt);&lt;br /&gt;
     warn &amp;quot;creating the new invoice\n&amp;quot; if $DEBUG;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#create the new invoice&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     my $cust_bill = new FS::cust_bill ( {&lt;br /&gt;
       'custnum'             &amp;lt;nowiki&amp;gt;=&amp;gt; $self-&amp;gt;custnum,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       '_date'               &amp;lt;nowiki&amp;gt;=&amp;gt; $invoice_time,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'charged'             &amp;lt;nowiki&amp;gt;=&amp;gt; $charged,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'billing_balance'     &amp;lt;nowiki&amp;gt;=&amp;gt; $balance,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'previous_balance'    &amp;lt;nowiki&amp;gt;=&amp;gt; $previous_balance,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'invoice_terms'       &amp;lt;nowiki&amp;gt;=&amp;gt; $options{'invoice_terms'},&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'cust_bill_pkg'       &amp;lt;nowiki&amp;gt;=&amp;gt; \@cust_bill_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       'pending'             &amp;lt;nowiki&amp;gt;=&amp;gt; 'Y', # clear this after doing taxes&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     } );&lt;br /&gt;
&lt;br /&gt;
     if (!$options{no_commit}) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# probably we ought to insert it as pending, and then rollback&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# without ever un-pending it&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $error = $cust_bill-&amp;gt;insert;&lt;br /&gt;
       if ( $error ) {&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return &amp;quot;can't create invoice for customer #&amp;quot;. $self-&amp;gt;custnum. &amp;quot;: $error&amp;quot;;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
== Calculate taxes and insert tax line items. ==&lt;br /&gt;
$tax_is_batch was previously set to true if we're using a batch tax processor. In that case, don't do any of this; just leave the invoice flagged as pending and FS::Cron::tax_batch will do the rest.&lt;br /&gt;
&lt;br /&gt;
Otherwise: get the tax engine we created for this invoice pass, and call calculate_taxes(), passing the pending invoice. This will return an arrayref of line items, or throw an exception. If it throws an exception, rollback and exit.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# calculate and append taxes&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     if ( ! $tax_is_batch) {&lt;br /&gt;
       local $@;&lt;br /&gt;
       my $arrayref = eval { $tax_engines{$pass}-&amp;gt;calculate_taxes($cust_bill) };&lt;br /&gt;
&lt;br /&gt;
       if ( $@ ) {&lt;br /&gt;
         $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
         return $@;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
Append each tax line item to the invoice (directly, by setting invnum and inserting them) and add the charges to a running total. Tax line items have 'setup' but not 'recur' charges.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# or should this be in TaxEngine?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       my $total_tax = 0;&lt;br /&gt;
       foreach my $taxline ( @$arrayref ) {&lt;br /&gt;
         $total_tax += $taxline-&amp;gt;setup;&lt;br /&gt;
         $taxline-&amp;gt;set('invnum' =&amp;gt; $cust_bill-&amp;gt;invnum); # just to be sure&lt;br /&gt;
         push @cust_bill_pkg, $taxline; # for return_bill&lt;br /&gt;
&lt;br /&gt;
         if (!$options{no_commit}) {&lt;br /&gt;
           my $error = $taxline-&amp;gt;insert;&lt;br /&gt;
           if ( $error ) {&lt;br /&gt;
             $dbh-&amp;gt;rollback if $oldAutoCommit;&lt;br /&gt;
             return $error;&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
Add the total tax to the total invoice charges, remove the pending flag, and then update the invoice record. Then we're done.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# add tax to the invoice amount and finalize it&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       ${ $total_setup{$pass} } = sprintf('%.2f', ${ $total_setup{$pass} } + $total_tax);&lt;br /&gt;
       $charged = sprintf('%.2f', $charged + $total_tax);&lt;br /&gt;
       $cust_bill-&amp;gt;set('charged', $charged);&lt;br /&gt;
       $cust_bill-&amp;gt;set('pending', '');&lt;br /&gt;
&lt;br /&gt;
       if (!$options{no_commit}) {&lt;br /&gt;
         my $error = $cust_bill-&amp;gt;replace;&lt;br /&gt;
         if ( $error ) {&lt;br /&gt;
           $dbh-&amp;gt;rollback if $oldAutoCommit;&lt;br /&gt;
           return $error;&lt;br /&gt;
         }&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
     } # if !$tax_is_batch&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# if it IS batch, then we'll do all this in process_tax_batch&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After dealing with this invoice, push it to the 'return_bill' arrayref if there is one. This will allow the caller to see which invoices were created if they want.&lt;br /&gt;
&lt;br /&gt;
     push @{$options{return_bill}}, $cust_bill if $options{return_bill};&lt;br /&gt;
&lt;br /&gt;
   } #foreach my $pass ( keys %cust_bill_pkg )&lt;br /&gt;
&lt;br /&gt;
Run any post-invoicing pre-commit hooks. These are currently used for only one thing: for packages with time-limited discounts, we need to increment the number of discount months used after completely billing the package, but before doing anything else. FS::part_pkg::discount_Mixin sets this up.&lt;br /&gt;
&lt;br /&gt;
Then commit changes if needed, and return.&lt;br /&gt;
&lt;br /&gt;
   foreach my $hook ( @precommit_hooks ) {&lt;br /&gt;
     eval {&lt;br /&gt;
       &amp;amp;{$hook}; #($self) ?&lt;br /&gt;
     } unless $options{no_commit};&lt;br /&gt;
     if ( $@ ) {&lt;br /&gt;
       $dbh-&amp;gt;rollback if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
       return &amp;quot;$@ running precommit hook $hook\n&amp;quot;;&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   $dbh-&amp;gt;commit or die $dbh-&amp;gt;errstr if $oldAutoCommit &amp;amp;&amp;amp; !$options{no_commit};&lt;br /&gt;
&lt;br /&gt;
   ''; #no error&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
= FS::cust_main::Billing::_make_lines =&lt;br /&gt;
This is the other most important billing method. It takes a single package and package definition, and creates at most one line item for that package. This is called only from bill() and should probably never be used any other way, since it updates the package's billing dates.&lt;br /&gt;
&lt;br /&gt;
== Gather parameters ==&lt;br /&gt;
$cust_pkg is the package. $part_pkg is the package definition, either the “real” one for this package or a billing add-on. We temporarily set the cust_pkg-&amp;gt;pkgpart property to match it so that we can call cust_pkg methods that delegate to “$self-&amp;gt;part_pkg”. We then copy all the cust_pkg's fields into %hash.&lt;br /&gt;
&lt;br /&gt;
$time is the “billing as of” date. %options is all the options that were passed to bill().&lt;br /&gt;
&lt;br /&gt;
The other $param{} elements are accumulators passed from outside so that this method can add stuff to them.&lt;br /&gt;
&lt;br /&gt;
$cust_location here isn't used for anything, because of tax engine refactoring.&lt;br /&gt;
&lt;br /&gt;
The reference to freq_override is one last attempt to make sure term discounts aren't set up in a radically absurd way.&lt;br /&gt;
&lt;br /&gt;
$lineitems is for remembering if we've done anything that requires creating a line item; if not then we'll exit without pushing one to the array.&lt;br /&gt;
&lt;br /&gt;
@details will be used for any detail records that we attach to this line item. Those are additional lines of text shown below the line item on the invoice.&lt;br /&gt;
&lt;br /&gt;
$cmp_time, as in bill(), is either “right now” or “the end of today” depending on the '''next-bill-ignore-time''' option.&lt;br /&gt;
&lt;br /&gt;
 sub _make_lines {&lt;br /&gt;
   my ($self, %params) = @_;&lt;br /&gt;
&lt;br /&gt;
   local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG &amp;gt; $DEBUG;&lt;br /&gt;
&lt;br /&gt;
   my $part_pkg = $params{part_pkg} or die &amp;quot;no part_pkg specified&amp;quot;;&lt;br /&gt;
   my $cust_pkg = $params{cust_pkg} or die &amp;quot;no cust_pkg specified&amp;quot;;&lt;br /&gt;
   my $cust_location = $cust_pkg-&amp;gt;tax_location;&lt;br /&gt;
   my $precommit_hooks = $params{precommit_hooks} or die &amp;quot;no precommit_hooks specified&amp;quot;;&lt;br /&gt;
   my $cust_bill_pkgs = $params{line_items} or die &amp;quot;no line buffer specified&amp;quot;;&lt;br /&gt;
   my $total_setup = $params{setup} or die &amp;quot;no setup accumulator specified&amp;quot;;&lt;br /&gt;
   my $total_recur = $params{recur} or die &amp;quot;no recur accumulator specified&amp;quot;;&lt;br /&gt;
   my $time = $params{'time'} or die &amp;quot;no time specified&amp;quot;;&lt;br /&gt;
   my (%options) = %{$params{options}};&lt;br /&gt;
&lt;br /&gt;
   my $tax_engine = $params{tax_engine};&lt;br /&gt;
&lt;br /&gt;
   if ( $part_pkg-&amp;gt;freq ne '1' and ($options{'freq_override'} || 0) &amp;gt; 0 ) {&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# this should never happen&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     die 'freq_override billing attempted on non-monthly package '.&lt;br /&gt;
       $cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   my $dbh = dbh;&lt;br /&gt;
   my $real_pkgpart = $params{real_pkgpart};&lt;br /&gt;
   my %hash = $cust_pkg-&amp;gt;hash;&lt;br /&gt;
   my $old_cust_pkg = new FS::cust_pkg \%hash;&lt;br /&gt;
&lt;br /&gt;
   my @details = ();&lt;br /&gt;
   my $lineitems = 0;&lt;br /&gt;
&lt;br /&gt;
   $cust_pkg-&amp;gt;pkgpart($part_pkg-&amp;gt;pkgpart);&lt;br /&gt;
&lt;br /&gt;
   my $cmp_time = ( $conf-&amp;gt;exists('next-bill-ignore-time')&lt;br /&gt;
                      ? day_end( $time )&lt;br /&gt;
                      : $time&lt;br /&gt;
                  );&lt;br /&gt;
&lt;br /&gt;
== Charge setup fee and set setup date ==&lt;br /&gt;
Setup fees are charged the first time a package is billed. %setup_param will be passed to the calc_setup() method; because of how discounts work, it needs to know if this is an add-on package, and to report if it's applying any discounts. $setup and $unitsetup will become those fields on the cust_bill_pkg record.&lt;br /&gt;
&lt;br /&gt;
The conditions for charging the setup fee are exactly as described in the inline comment. Note that a package that's canceled before first billing will never be charged a setup fee (or any other fee). We assume the package was ordered by mistake.&lt;br /&gt;
&lt;br /&gt;
Note also that if the package has an expire date &amp;lt;= $cmp_time then it won't be charged. Normally the package would be canceled already. This check is for the case where the package isn't expired yet (in real time) but is being billed for a date after it will expire. For example, if a package has a next bill date of Feb 1, but an expiration date of Jan 31, and you're running freeside-daily -y 3 to print invoices 3 days in advance, then billing will trigger for the customer on Jan 28. The package won't be canceled yet. However, we should ''not'' preprint an invoice for this package because it ''will'' expire before its real billing date arrives. This is a real case that has come up.&lt;br /&gt;
&lt;br /&gt;
Possibly we should check adjourn dates also, since a suspended package is not supposed to get set up.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# bill setup&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my $setup = 0;&lt;br /&gt;
   my $unitsetup = 0;&lt;br /&gt;
   my @setup_discounts = ();&lt;br /&gt;
   my %setup_param = ( 'discounts'     &amp;lt;nowiki&amp;gt;=&amp;gt; \@setup_discounts,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                       'real_pkgpart'  &amp;lt;nowiki&amp;gt;=&amp;gt; $params{real_pkgpart}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                     );&lt;br /&gt;
   my $setup_billed_currency = '';&lt;br /&gt;
   my $setup_billed_amount = 0;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# Conditions for setting setup date and charging the setup fee:&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - this is not a recurring-only billing run&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and the package is not currently being canceled&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and, unless we're specifically told otherwise via 'resetup':&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - it doesn't already HAVE a setup date&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or a start date in the future&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - and it's not suspended&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and it doesn't have an expire date in the past&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# The &amp;quot;disable_setup_suspended&amp;quot; option is now obsolete; we never set the&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# setup date on a suspended package.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   if (     ! $options{recurring_only}&lt;br /&gt;
        and ! $options{cancel}&lt;br /&gt;
        and ( $options{'resetup'}&lt;br /&gt;
              || ( ! $cust_pkg-&amp;gt;setup&lt;br /&gt;
                   &amp;amp;&amp;amp; ( ! $cust_pkg-&amp;gt;start_date&lt;br /&gt;
                        || $cust_pkg-&amp;gt;start_date &amp;lt;= $cmp_time&lt;br /&gt;
                      )&lt;br /&gt;
                   &amp;amp;&amp;amp; ( ! $cust_pkg-&amp;gt;getfield('susp') )&lt;br /&gt;
                 )&lt;br /&gt;
            )&lt;br /&gt;
        and ( ! $cust_pkg-&amp;gt;expire&lt;br /&gt;
              || $cust_pkg-&amp;gt;expire &amp;gt; $cmp_time )&lt;br /&gt;
      )&lt;br /&gt;
   {&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;    bill setup\n&amp;quot; if $DEBUG &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
If 'waive_setup' is set on the package then skip this step.&lt;br /&gt;
&lt;br /&gt;
Calculate the setup fee. FS::cust_pkg-&amp;gt;calc_setup just delegates to its part_pkg (which we've overridden if we're processing an add-on package right now). The following part_pkg classes have a calc_setup method:&lt;br /&gt;
&lt;br /&gt;
* flat and recur_Common: Calls prorate_setup(), about which [#Deferred prorate see below]. Adds any “additional_info” strings to @details (this is used for putting notes on one-time charges). Calls base_setup() to get the setup fee from the package definition, then calc_discount() to apply any discounts, then multiplies the result by quantity. Almost all packages use this logic.&lt;br /&gt;
* currency_fixed: Calls prorate_setup(), then base_setup() (which does the currency conversion). Oddly, it doesn't multiply by quantity; this is probably a bug, since it does multiply by quantity in calc_recur().&lt;br /&gt;
* delayed_Mixin: If the 'delay_setup' option is off, pushes back the package bill date by some number of days so that the start of recurring billing is delayed. Then calls whatever calc_setup() method would otherwise apply.&lt;br /&gt;
* rt_field and (the obsolete) voip_sqlradacct: Returns the 'setup_fee' package option, without multiplying by quantity or applying discounts.&lt;br /&gt;
&lt;br /&gt;
Also calculate the per-unit setup fee (for displaying on the invoice). The base_setup() method, by definition, returns the non-discounted per-unit setup fee.&lt;br /&gt;
&lt;br /&gt;
Currency packages also return (via $setup_param) the currency and amount before doing conversion; we remember those here and will put them on the invoice later.&lt;br /&gt;
&lt;br /&gt;
     unless ( $cust_pkg-&amp;gt;waive_setup ) {&lt;br /&gt;
         $lineitems++;&lt;br /&gt;
&lt;br /&gt;
         $setup = eval { $cust_pkg-&amp;gt;calc_setup( $time, \@details, \%setup_param ) };&lt;br /&gt;
         return &amp;quot;$@ running calc_setup for $cust_pkg\n&amp;quot;&lt;br /&gt;
           if $@;&lt;br /&gt;
&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# Only increment unitsetup here if there IS a setup fee.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# prorate_defer_bill may cause calc_setup on a setup-stage package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# to return zero, and the setup fee to be charged later. (This happens&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# when it's first billed on the prorate cutoff day. RT#31276.)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         if ( $setup ) {&lt;br /&gt;
           $unitsetup = $cust_pkg-&amp;gt;base_setup()&lt;br /&gt;
                          || $setup; #XXX uuh&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
         if ( $setup_param{'billed_currency'} ) {&lt;br /&gt;
           $setup_billed_currency = delete $setup_param{'billed_currency'};&lt;br /&gt;
           $setup_billed_amount   &amp;lt;nowiki&amp;gt;= delete $setup_param{'billed_amount'};&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Assign the setup date. The package may already have a setup date (because of 'resetup'). Otherwise, if it has a designated start date, use that (and clear the start date). Otherwise, it started billing now so set it to now.&lt;br /&gt;
&lt;br /&gt;
     if ( $cust_pkg-&amp;gt;get('setup') ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# don't change it&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     } elsif ( $cust_pkg-&amp;gt;get('start_date') ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# this allows start_date to be used to set the first bill date&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $cust_pkg-&amp;gt;set('setup', $cust_pkg-&amp;gt;get('start_date'));&lt;br /&gt;
     } else {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# if unspecified, start it right now&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $cust_pkg-&amp;gt;set('setup', $time);&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $cust_pkg-&amp;gt;setfield('start_date', '')&lt;br /&gt;
       if $cust_pkg-&amp;gt;start_date;&lt;br /&gt;
&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
=== Deferred prorate ===&lt;br /&gt;
This is an optional mode for prorate packages (and flat monthly packages using sync_bill_date), enabled by the prorate_defer_bill option. Rather than charging a fractional month at setup time, the package is billed on the first cutoff day for a full month ''plus'' the fractional month preceding the cutoff day.&lt;br /&gt;
&lt;br /&gt;
In packages that are eligible for deferred prorate, calc_setup() calls the calc_prorate() method, which checks whether the flag is present. If so, calc_prorate sets the setup date (to now) and the next bill date (to the upcoming cutoff day) but doesn't set last_bill because the package hasn't really been billed. Then it returns a true value, which tells calc_setup ''not'' to charge a setup fee. The package will then not be charged a recurring fee either, because the next bill date is in the future.&lt;br /&gt;
&lt;br /&gt;
(If the setup date is itself a cutoff day, then calc_prorate will ignore the flag and bill the package for a full month as usual. It is highly recommended that you use the prorate_round_day option in combination with this so that the “full month” really is a full month.)&lt;br /&gt;
&lt;br /&gt;
When the first recurring billing date arrives, calc_recur() notices that there's a bill date but no last_bill, and charges for the partial month in the past and full month in the future. (The start and end dates on the line item will reflect this.) It will also call calc_setup() and apply any setup fee defined by the package.&lt;br /&gt;
&lt;br /&gt;
== Charge the recurring fee and adjust the bill date ==&lt;br /&gt;
The comment here explains the logic for whether to charge a recurring fee.&lt;br /&gt;
&lt;br /&gt;
At this point, if it still has a start_date, then either the start_date is in the future or the package couldn't start billing for some other reason (like that it's on hold).&lt;br /&gt;
&lt;br /&gt;
See the note above about the expire date; this part is slightly different in that if the package actually ''has'' expired and we're billing it on cancellation, then charging a recurring fee is allowed.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# bill recurring fee&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;### &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   my $recur = 0;&lt;br /&gt;
   my $unitrecur = 0;&lt;br /&gt;
   my @recur_discounts = ();&lt;br /&gt;
   my $recur_billed_currency = '';&lt;br /&gt;
   my $recur_billed_amount = 0;&lt;br /&gt;
   my $sdate;&lt;br /&gt;
&lt;br /&gt;
   my $override_quantity;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# Conditions for billing the recurring fee:&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - the package doesn't have a future start date&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and it's not suspended&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - unless suspend_bill is enabled on the package or package def&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;    - but still not, if the package is on hold&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's suspended for a delayed cancellation&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and its next bill date is in the past&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it doesn't have a next bill date yet&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's a one-time charge&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's a CDR plan with the &amp;quot;bill_every_call&amp;quot; option&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - or it's being canceled&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# - and it doesn't have an expire date in the past (this can happen with&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  advance billing)&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# &amp;lt;/nowiki&amp;gt;  - again, unless it's being canceled&lt;br /&gt;
   if (     ! $cust_pkg-&amp;gt;start_date&lt;br /&gt;
        and&lt;br /&gt;
            ( ! $cust_pkg-&amp;gt;susp&lt;br /&gt;
                || ( $cust_pkg-&amp;gt;susp != $cust_pkg-&amp;gt;order_date&lt;br /&gt;
                       &amp;amp;&amp;amp; (    $cust_pkg-&amp;gt;option('suspend_bill',1)&lt;br /&gt;
                            || ( $part_pkg-&amp;gt;option('suspend_bill', 1)&lt;br /&gt;
                                  &amp;amp;&amp;amp; ! $cust_pkg-&amp;gt;option('no_suspend_bill',1)&lt;br /&gt;
                               )&lt;br /&gt;
                          )&lt;br /&gt;
                   )&lt;br /&gt;
                || $cust_pkg-&amp;gt;is_status_delay_cancel&lt;br /&gt;
            )&lt;br /&gt;
        and&lt;br /&gt;
             ( $part_pkg-&amp;gt;freq ne '0' &amp;amp;&amp;amp; ( $cust_pkg-&amp;gt;bill || 0 ) &amp;lt;= $cmp_time )&lt;br /&gt;
          || ( $part_pkg-&amp;gt;plan eq 'voip_cdr'&lt;br /&gt;
                &amp;amp;&amp;amp; $part_pkg-&amp;gt;option('bill_every_call')&lt;br /&gt;
             )&lt;br /&gt;
          || $options{cancel}&lt;br /&gt;
&lt;br /&gt;
        and&lt;br /&gt;
           ( ! $cust_pkg-&amp;gt;expire&lt;br /&gt;
             || $cust_pkg-&amp;gt;expire &amp;gt; $cmp_time&lt;br /&gt;
             || $options{cancel}&lt;br /&gt;
           )&lt;br /&gt;
   ) {&lt;br /&gt;
&lt;br /&gt;
Reset the package's usage cap if it has one:&lt;br /&gt;
&lt;br /&gt;
* For voip_cdr, this is for “usage pools”. It clears all cdr_cust_pkg_usage records from the package and resets the cust_pkg_usage's minutes remaining.&lt;br /&gt;
* For flat (and other flat-like packages), this finds svc_accts attached to the package and resets their byte usage counters. This is for RADIUS services with data caps.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# XXX should this be a package event? &amp;lt;/nowiki&amp;gt; probably.  events are called&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# at collection time at the moment, though...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $part_pkg-&amp;gt;reset_usage($cust_pkg, 'debug'=&amp;gt;$DEBUG)&lt;br /&gt;
       if $part_pkg-&amp;gt;can('reset_usage') &amp;amp;&amp;amp; !$options{'no_usage_reset'};&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#don't want to reset usage just cause we want a line item??&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#&amp;amp;&amp;amp; $part_pkg-&amp;gt;pkgpart == $real_pkgpart;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
At this point we've decided to charge a recurring fee, so increment $lineitems.&lt;br /&gt;
&lt;br /&gt;
“$sdate” is the effective billing date. It represents the end of the previous cycle and the start of the next. The next bill date will be set to $sdate + one billing period.&lt;br /&gt;
&lt;br /&gt;
For a new package, $sdate is the setup date (so, the start date if there was one); if it's been billed before then it's the billing date.&lt;br /&gt;
&lt;br /&gt;
Prorate math will normally charge for the period from $sdate to the next prorate cycle date after that. After that, it will set the next bill date to the prorate cycle date, so that future billing cyles run from one prorate cycle date to the next. The result is that the customer is charged for a partial month, then for full months after that.&lt;br /&gt;
&lt;br /&gt;
$increment_next_bill is, yes, a flag saying to increment the bill date. We won't increment for one-time charges, or packages that are being canceled. The check for the bill date &amp;lt;= $cmp_time is now redundant, since we won't bill the package at all.&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;    bill recur\n&amp;quot; if $DEBUG &amp;gt; 1;&lt;br /&gt;
     $lineitems++;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# XXX shared with $recur_prog&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $sdate = ( $options{cancel} ? $cust_pkg-&amp;gt;last_bill : $cust_pkg-&amp;gt;bill )&lt;br /&gt;
              || $cust_pkg-&amp;gt;setup&lt;br /&gt;
              || $time;&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#over two params! &amp;lt;/nowiki&amp;gt; lets at least switch to a hashref for the rest...&lt;br /&gt;
     my $increment_next_bill = ( $part_pkg-&amp;gt;freq ne '0'&lt;br /&gt;
                                 &amp;amp;&amp;amp; ( $cust_pkg-&amp;gt;getfield('bill') || 0 ) &amp;lt;= $cmp_time&lt;br /&gt;
                                 &amp;amp;&amp;amp; !$options{cancel}&lt;br /&gt;
                               );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The most important part of the entire module: calculate the recurring fee. Call FS::part_pkg::calc_recur (or calc_cancel, if we're canceling the package).&lt;br /&gt;
&lt;br /&gt;
This is ''not a well-behaved interface''. The arguments are all passed by reference (they're used to pass values back out), and they include the contents of %setup_param, so packages can use the %param hashref argument to pass notes between the calc_setup and calc_recur stages. Discounts, especially, use this.&lt;br /&gt;
&lt;br /&gt;
* $sdate is the effective bill date, @details is the array of line item details, and %param contains everything else.&lt;br /&gt;
* 'discounts' is an arrayref of objects that will be inserted with the line item to track which discounts applied.&lt;br /&gt;
* 'precommit_hooks' and 'real_pkgpart' are used to keep track of the months remaining on term-limited discounts: if real_pkgpart matches the pkgpart of the package, then it's not an add-on package, so a month (or other amount) should be deducted. In that case a callback will be appended to precommit_hooks to do that at the end.&lt;br /&gt;
* 'freq_override' should be ignored.&lt;br /&gt;
* 'setup_fee' allows calc_recur to charge a setup fee. This seems bizarre but is needed for certain prorate cases.&lt;br /&gt;
* 'increment_next_bill' is checked in recur_Common to decide whether to apply the fixed recurring charge for voip_cdr and other usage-based packages. The intent seems to be to not bill those charges on cancellation.&lt;br /&gt;
&lt;br /&gt;
     my %param = ( %setup_param,&lt;br /&gt;
                   'precommit_hooks'     &amp;lt;nowiki&amp;gt;=&amp;gt; $precommit_hooks,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                   'increment_next_bill' =&amp;gt; $increment_next_bill,&lt;br /&gt;
                   'discounts'           &amp;lt;nowiki&amp;gt;=&amp;gt; \@recur_discounts,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                   'real_pkgpart'        &amp;lt;nowiki&amp;gt;=&amp;gt; $real_pkgpart,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                   'freq_override' =&amp;gt; $options{freq_override} || '',&lt;br /&gt;
                   'setup_fee'           &amp;lt;nowiki&amp;gt;=&amp;gt; 0,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
                 );&lt;br /&gt;
&lt;br /&gt;
     my $method = $options{cancel} ? 'calc_cancel' : 'calc_recur';&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# There may be some part_pkg for which this is wrong. &amp;lt;/nowiki&amp;gt; Only those&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# which can_discount are supported.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;# (the UI should prevent adding discounts to these at the moment)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
     warn &amp;quot;calling $method on cust_pkg &amp;quot;. $cust_pkg-&amp;gt;pkgnum.&lt;br /&gt;
          &amp;quot; for pkgpart &amp;quot;. $cust_pkg-&amp;gt;pkgpart.&lt;br /&gt;
          &amp;quot; with params &amp;quot;. join(' / ', map &amp;quot;$_=&amp;gt;$param{$_}&amp;quot;, keys %param). &amp;quot;\n&amp;quot;&lt;br /&gt;
       if $DEBUG &amp;gt; 2;&lt;br /&gt;
&lt;br /&gt;
     $recur = eval { $cust_pkg-&amp;gt;$method( \$sdate, \@details, \%param ) };&lt;br /&gt;
     return &amp;quot;$@ running $method for $cust_pkg\n&amp;quot;&lt;br /&gt;
       if ( $@ );&lt;br /&gt;
&lt;br /&gt;
Handle a special case where calc_cancel() finds that the package isn't eligible to be billed on cancellation. This affects the decision about whether to create a line item at all.&lt;br /&gt;
&lt;br /&gt;
     if ($recur eq 'NOTHING') {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# then calc_cancel (or calc_recur but that's not used) has declined to&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# generate a recurring lineitem at all. treat this as zero, but also &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# try not to generate a lineitem.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $recur = 0;&lt;br /&gt;
       $lineitems--;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
Process more things returned by calc_recur. Ask the package for the unit recurring charge (passing $sdate because packages with an intro rate will report a different amount in the intro period).&lt;br /&gt;
&lt;br /&gt;
If calc_recur did a currency conversion, record how it happened.&lt;br /&gt;
&lt;br /&gt;
If calc_recur reported a quantity, record that quantity and adjust unitrecur. sql_external packages can do this.&lt;br /&gt;
&lt;br /&gt;
     &amp;lt;nowiki&amp;gt;#base_cancel???&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     $unitrecur = $cust_pkg-&amp;gt;base_recur( \$sdate ) || $recur; #XXX uuh, better&lt;br /&gt;
&lt;br /&gt;
     if ( $param{'billed_currency'} ) {&lt;br /&gt;
       $recur_billed_currency = delete $param{'billed_currency'};&lt;br /&gt;
       $recur_billed_amount   &amp;lt;nowiki&amp;gt;= delete $param{'billed_amount'};&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     if ( $param{'override_quantity'} ) {&lt;br /&gt;
       $override_quantity = $param{'override_quantity'};&lt;br /&gt;
       $unitrecur = $recur / $override_quantity;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
The other most important part of the module: increment the next bill date so that the package cycles.&lt;br /&gt;
&lt;br /&gt;
As always in Freeside, adding some period to a bill date is done by the FS::part_pkg-&amp;gt;add_freq method.&lt;br /&gt;
&lt;br /&gt;
Deal with the case where this is a supplemental package. A supplemental package is “geared” to its main package: there's a fixed frequency ratio between them so that every N cycles they bill on the same day. If it's an integer multiple then that's easy (just add the main package period that man times). Otherwise, check whether the next bill is when they're due to sync up, and if so, sync them. This uses a rather crude “they're within 15 days” heuristic.&lt;br /&gt;
&lt;br /&gt;
Otherwise, just call add_freq() using this package's frequency. Note that if the package is being prorated, $sdate will already have been moved to a prorate cycle date so that adding one month yields the right result.&lt;br /&gt;
&lt;br /&gt;
     if ( $increment_next_bill ) {&lt;br /&gt;
&lt;br /&gt;
       my $next_bill;&lt;br /&gt;
&lt;br /&gt;
       if ( my $main_pkg = $cust_pkg-&amp;gt;main_pkg ) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# supplemental package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# to keep in sync with the main package, simulate billing at &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# its frequency&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         my $main_pkg_freq = $main_pkg-&amp;gt;part_pkg-&amp;gt;freq;&lt;br /&gt;
         my $supp_pkg_freq = $part_pkg-&amp;gt;freq;&lt;br /&gt;
         if ( $supp_pkg_freq == 0 or $main_pkg_freq == 0 ) {&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# the UI should prevent setting up packages like this, but just&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# in case&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           return &amp;quot;unable to calculate supplemental package period ratio&amp;quot;;&lt;br /&gt;
         }&lt;br /&gt;
         my $ratio = $supp_pkg_freq / $main_pkg_freq;&lt;br /&gt;
         if ( $ratio == int($ratio) ) {&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# simple case: main package is X months, supp package is X*A months,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# advance supp package to where the main package will be in A cycles.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           $next_bill = $sdate;&lt;br /&gt;
           for (1..$ratio) {&lt;br /&gt;
             $next_bill = $part_pkg-&amp;gt;add_freq( $next_bill, $main_pkg_freq );&lt;br /&gt;
           }&lt;br /&gt;
         } else {&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# harder case: main package is X months, supp package is Y months.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# advance supp package by Y months. then if they're within half a &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# month of each other, resync them. this may result in the period&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           &amp;lt;nowiki&amp;gt;# not being exactly Y months.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
           $next_bill = $part_pkg-&amp;gt;add_freq( $sdate, $supp_pkg_freq );&lt;br /&gt;
           my $main_next_bill = $main_pkg-&amp;gt;bill;&lt;br /&gt;
           if ( $main_pkg-&amp;gt;bill &amp;lt;= $time ) {&lt;br /&gt;
             &amp;lt;nowiki&amp;gt;# then the main package has not yet been billed on this cycle;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
             &amp;lt;nowiki&amp;gt;# predict what its bill date will be.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
             $main_next_bill =&lt;br /&gt;
               $part_pkg-&amp;gt;add_freq( $main_next_bill, $main_pkg_freq );&lt;br /&gt;
           }&lt;br /&gt;
           if ( abs($main_next_bill - $next_bill) &amp;lt; 86400*15 ) {&lt;br /&gt;
             $next_bill = $main_next_bill;&lt;br /&gt;
           }&lt;br /&gt;
         }&lt;br /&gt;
&lt;br /&gt;
       } else {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# the normal case, not a supplemental package&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $next_bill = $part_pkg-&amp;gt;add_freq($sdate, $options{freq_override} || 0);&lt;br /&gt;
       return &amp;quot;unparsable frequency: &amp;quot;.&lt;br /&gt;
         ($options{freq_override} || $part_pkg-&amp;gt;freq)&lt;br /&gt;
         if $next_bill == -1;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
Set the “last bill” date to either the previous value of “next bill” or the setup date.&lt;br /&gt;
&lt;br /&gt;
Also, if there's any second-pass setup fee, apply that to the line item.&lt;br /&gt;
&lt;br /&gt;
'discount_left_setup' is a register used by the discount code for any flat-amount discount that's available for applying to the setup fee. Apply it here. This probably should set up a cust_bill_pkg_discount record but it's such an obscure case that we haven't needed it yet.&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#pro-rating magic - if $recur_prog fiddled $sdate, want to use that&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# only for figuring next bill date, nothing else, so, reset $sdate again&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# here&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $sdate = $cust_pkg-&amp;gt;bill || $cust_pkg-&amp;gt;setup || $time;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;#no need, its in $hash{last_bill}# my $last_bill = $cust_pkg-&amp;gt;last_bill;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $cust_pkg-&amp;gt;last_bill($sdate);&lt;br /&gt;
&lt;br /&gt;
       $cust_pkg-&amp;gt;setfield('bill', $next_bill );&lt;br /&gt;
&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     if ( $param{'setup_fee'} ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# Add an additional setup fee at the billing stage.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# Used for prorate_defer_bill.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       $setup += $param{'setup_fee'};&lt;br /&gt;
       $unitsetup = $cust_pkg-&amp;gt;base_setup();&lt;br /&gt;
       $lineitems++;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     if ( defined $param{'discount_left_setup'} ) {&lt;br /&gt;
         foreach my $discount_setup ( values %{$param{'discount_left_setup'}} ) {&lt;br /&gt;
             $setup -= $discount_setup;&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
   } # end of recurring fee&lt;br /&gt;
&lt;br /&gt;
   warn &amp;quot;\$setup is undefined&amp;quot; unless defined($setup);&lt;br /&gt;
   warn &amp;quot;\$recur is undefined&amp;quot; unless defined($recur);&lt;br /&gt;
   warn &amp;quot;\$cust_pkg-&amp;gt;bill is undefined&amp;quot; unless defined($cust_pkg-&amp;gt;bill);&lt;br /&gt;
&lt;br /&gt;
== Construct a cust_bill_pkg record ==&lt;br /&gt;
If $lineitems is zero then billing was entirely skipped and there's nothing to do.&lt;br /&gt;
&lt;br /&gt;
Otherwise, save the cust_pkg record with the new bill and last_bill dates. Only do that when processing the base package; add-on packages don't increment the bill date.&lt;br /&gt;
&lt;br /&gt;
Clean up $setup and $recur. The 'allow_negative_charges' config is no longer used.&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# If there's line items, create em cust_bill_pkg records&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;# If $cust_pkg has been modified, update it (if we're a real pkgpart)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   if ( $lineitems ) {&lt;br /&gt;
&lt;br /&gt;
     if ( $cust_pkg-&amp;gt;modified &amp;amp;&amp;amp; $cust_pkg-&amp;gt;pkgpart == $real_pkgpart ) {&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# hmm.. and if just the options are modified in some weird price plan?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
       warn &amp;quot;  package &amp;quot;. $cust_pkg-&amp;gt;pkgnum. &amp;quot; modified; updating\n&amp;quot;&lt;br /&gt;
         if $DEBUG &amp;gt;1;&lt;br /&gt;
&lt;br /&gt;
       my $error = $cust_pkg-&amp;gt;replace( $old_cust_pkg,&lt;br /&gt;
                                       'depend_jobnum'=&amp;gt;$options{depend_jobnum},&lt;br /&gt;
                                       'options' =&amp;gt; { $cust_pkg-&amp;gt;options },&lt;br /&gt;
                                     )&lt;br /&gt;
         unless $options{no_commit};&lt;br /&gt;
       return &amp;quot;Error modifying pkgnum &amp;quot;. $cust_pkg-&amp;gt;pkgnum. &amp;quot;: $error&amp;quot;&lt;br /&gt;
         if $error; #just in case&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $setup = sprintf( &amp;quot;%.2f&amp;quot;, $setup );&lt;br /&gt;
     $recur = sprintf( &amp;quot;%.2f&amp;quot;, $recur );&lt;br /&gt;
     if ( $setup &amp;lt; 0 &amp;amp;&amp;amp; ! $conf-&amp;gt;exists('allow_negative_charges') ) {&lt;br /&gt;
       return &amp;quot;negative setup $setup for pkgnum &amp;quot;. $cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
     }&lt;br /&gt;
     if ( $recur &amp;lt; 0 &amp;amp;&amp;amp; ! $conf-&amp;gt;exists('allow_negative_charges') ) {&lt;br /&gt;
       return &amp;quot;negative recur $recur for pkgnum &amp;quot;. $cust_pkg-&amp;gt;pkgnum;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
One more check. If the setup and recurring fees are both zero, we might skip creating a line item. It depends. Create a zero-dollar line item if:&lt;br /&gt;
&lt;br /&gt;
* The setup or recur is zero ''because it was discounted to zero'', and the '''discount-show-always''' config is on. The invoice will then show the applied discount.&lt;br /&gt;
* This is the main package in a ''bundle''. Its add-on packages will add hidden line items for the charges, but we need this one so that the bundle is shown with the correct package name.&lt;br /&gt;
* Either the cust_pkg or the part_pkg has the 'setup_show_zero' or 'recur_show_zero' flag. (It's not clear to me why these are separate flags, since they have exactly the same effect.)&lt;br /&gt;
&lt;br /&gt;
     my $discount_show_always = $conf-&amp;gt;exists('discount-show-always')&lt;br /&gt;
                                &amp;amp;&amp;amp; (    ($setup == 0 &amp;amp;&amp;amp; scalar(@setup_discounts))&lt;br /&gt;
                                     || ($recur == 0 &amp;amp;&amp;amp; scalar(@recur_discounts))&lt;br /&gt;
                                   );&lt;br /&gt;
&lt;br /&gt;
     if (    $setup != 0&lt;br /&gt;
          || $recur != 0&lt;br /&gt;
          || (!$part_pkg-&amp;gt;hidden &amp;amp;&amp;amp; $options{has_hidden}) #include some $0 lines&lt;br /&gt;
          || $discount_show_always&lt;br /&gt;
          || ($setup == 0 &amp;amp;&amp;amp; $cust_pkg-&amp;gt;_X_show_zero('setup'))&lt;br /&gt;
          || ($recur == 0 &amp;amp;&amp;amp; $cust_pkg-&amp;gt;_X_show_zero('recur'))&lt;br /&gt;
        )&lt;br /&gt;
     {&lt;br /&gt;
&lt;br /&gt;
       warn &amp;quot;    charges (setup=$setup, recur=$recur); adding line items\n&amp;quot;&lt;br /&gt;
         if $DEBUG &amp;gt; 1;&lt;br /&gt;
&lt;br /&gt;
Find any package details that are set to display as “invoice details” and add them to the list.&lt;br /&gt;
&lt;br /&gt;
Then actually create the cust_bill_pkg. Huzzah!&lt;br /&gt;
&lt;br /&gt;
       my @cust_pkg_detail = map { $_-&amp;gt;detail } $cust_pkg-&amp;gt;cust_pkg_detail('I');&lt;br /&gt;
       if ( $DEBUG &amp;gt; 1 ) {&lt;br /&gt;
         warn &amp;quot;      adding customer package invoice detail: $_\n&amp;quot;&lt;br /&gt;
           foreach @cust_pkg_detail;&lt;br /&gt;
       }&lt;br /&gt;
       push @details, @cust_pkg_detail;&lt;br /&gt;
       my $cust_bill_pkg = new FS::cust_bill_pkg {&lt;br /&gt;
         'pkgnum'                &amp;lt;nowiki&amp;gt;=&amp;gt; $cust_pkg-&amp;gt;pkgnum,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'setup'                 &amp;lt;nowiki&amp;gt;=&amp;gt; $setup,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'unitsetup'             &amp;lt;nowiki&amp;gt;=&amp;gt; sprintf('%.2f', $unitsetup),&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'setup_billed_currency' =&amp;gt; $setup_billed_currency,&lt;br /&gt;
         'setup_billed_amount'   &amp;lt;nowiki&amp;gt;=&amp;gt; $setup_billed_amount,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'recur'                 &amp;lt;nowiki&amp;gt;=&amp;gt; $recur,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'unitrecur'             &amp;lt;nowiki&amp;gt;=&amp;gt; sprintf('%.2f', $unitrecur),&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'recur_billed_currency' =&amp;gt; $recur_billed_currency,&lt;br /&gt;
         'recur_billed_amount'   &amp;lt;nowiki&amp;gt;=&amp;gt; $recur_billed_amount,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'quantity'              &amp;lt;nowiki&amp;gt;=&amp;gt; $override_quantity || $cust_pkg-&amp;gt;quantity,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'details'               &amp;lt;nowiki&amp;gt;=&amp;gt; \@details,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'discounts'             &amp;lt;nowiki&amp;gt;=&amp;gt; [ @setup_discounts, @recur_discounts ],&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'hidden'                &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg-&amp;gt;hidden,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         'freq'                  &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg-&amp;gt;freq,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       };&lt;br /&gt;
&lt;br /&gt;
Determine the start and end of the billing period. Usually it's “last bill to current bill”, or “current bill to next bill”, but there are a couple of special cases. Also record which part_pkg this line item was generated for, if it's an add-on package.&lt;br /&gt;
&lt;br /&gt;
       if ( $part_pkg-&amp;gt;option('prorate_defer_bill',1)&lt;br /&gt;
            and !$hash{last_bill} ) {&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;# both preceding and upcoming, technically&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;sdate( $cust_pkg-&amp;gt;setup );&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $cust_pkg-&amp;gt;bill );&lt;br /&gt;
       } elsif ( $part_pkg-&amp;gt;recur_temporality eq 'preceding' ) {&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;sdate( $hash{last_bill} );&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $sdate - 86399   ); #60s*60m*24h-1&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $time ) if $options{cancel};&lt;br /&gt;
       } else { #if ( $part_pkg-&amp;gt;recur_temporality eq 'upcoming' )&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;sdate( $sdate );&lt;br /&gt;
         $cust_bill_pkg-&amp;gt;edate( $cust_pkg-&amp;gt;bill );&lt;br /&gt;
         &amp;lt;nowiki&amp;gt;#$cust_bill_pkg-&amp;gt;edate( $time ) if $options{cancel};&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       }&lt;br /&gt;
&lt;br /&gt;
       $cust_bill_pkg-&amp;gt;pkgpart_override($part_pkg-&amp;gt;pkgpart)&lt;br /&gt;
         unless $part_pkg-&amp;gt;pkgpart == $real_pkgpart;&lt;br /&gt;
&lt;br /&gt;
Record the results in the right places: add setup and recur to totals, give the cust_bill_pkg to the tax calculator, and add it to the line item array.&lt;br /&gt;
&lt;br /&gt;
       $$total_setup += $setup;&lt;br /&gt;
       $$total_recur += $recur;&lt;br /&gt;
&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;# handle taxes&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
       &amp;lt;nowiki&amp;gt;###&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
       my $error = $tax_engine-&amp;gt;add_sale($cust_bill_pkg);&lt;br /&gt;
       return $error if $error;&lt;br /&gt;
&lt;br /&gt;
       $cust_bill_pkg-&amp;gt;set_display(&lt;br /&gt;
         part_pkg     &amp;lt;nowiki&amp;gt;=&amp;gt; $part_pkg,&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
         real_pkgpart =&amp;gt; $real_pkgpart,&lt;br /&gt;
       );&lt;br /&gt;
&lt;br /&gt;
       push @$cust_bill_pkgs, $cust_bill_pkg;&lt;br /&gt;
&lt;br /&gt;
     } #if $setup != 0 || $recur != 0&lt;br /&gt;
&lt;br /&gt;
   } #if $line_items&lt;br /&gt;
&lt;br /&gt;
   '';&lt;br /&gt;
&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
And we're done!&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Category:Freeside_5&amp;diff=9642</id>
		<title>Category:Freeside 5</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Category:Freeside_5&amp;diff=9642"/>
				<updated>2017-01-23T17:54:47Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;Pages related to Freeside version 5 (the master branch as of 2017).&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pages related to Freeside version 5 (the master branch as of 2017).&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:5:Documentation:Tower_mapping&amp;diff=9641</id>
		<title>Freeside:5:Documentation:Tower mapping</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:5:Documentation:Tower_mapping&amp;diff=9641"/>
				<updated>2017-01-23T17:54:09Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This document describes the tower coverage features on the 5.x branch, which has not yet been released. The tower coverage system works, but further development is planned. Red text is for notes on future improvements.&lt;br /&gt;
&lt;br /&gt;
= Tower coverage calculation =&lt;br /&gt;
The goals of tower coverage calculation are:&lt;br /&gt;
&lt;br /&gt;
* ''Mapping'': estimating a tower's coverage area, for marketing, deployment planning, and regulatory reporting.&lt;br /&gt;
* ''Prequalification'': estimating the quality of signal available at a customer site, prior to doing an on-site survey.&lt;br /&gt;
&lt;br /&gt;
These require the same calculation: evaluating a signal strength function at a geographic point, using the tower's transmitter characteristics and a terrain map, and comparing to a minimum specified signal strength:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;{P}_{\mathit{RX},\mathit{\min }}\le {P}_{\mathit{TX}}+{G}_{\mathit{TX}}-{L}_{\mathit{path}}+{G}_{\mathit{RX}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where ''P&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; is the transmitter power, ''G&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; and ''G&amp;lt;sub&amp;gt;RX''&amp;lt;/sub&amp;gt; are the transmitter and receiver antenna gains, and ''L&amp;lt;sub&amp;gt;path''&amp;lt;/sub&amp;gt; is the path loss. For an unobstructed path length ''d ''and a frequency ''f'' , the path loss is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;{L}_{\mathit{path}}=20{\log }_{10}4\mathrm{\pi }df/c\approx 36.6\mathrm{dB}+20{\log }_{10}f-20{\log }_{10}d&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where ''f'' is in MHz and ''d'' is in miles. For a partially obstructed path, this is more complicated; note that the path is significantly wider than the physical antenna due to diffraction. Consult your radio engineer for details.&lt;br /&gt;
&lt;br /&gt;
Tower configuration is in '''Configuration / Services / Wireless broadband / Towers'''.&lt;br /&gt;
&lt;br /&gt;
= Tower/sector schema =&lt;br /&gt;
A record in “tower” represents a site where broadband equipment is installed, including:&lt;br /&gt;
&lt;br /&gt;
* Latitude and longitude&lt;br /&gt;
* Tower elevation&lt;br /&gt;
&lt;br /&gt;
A record in “tower_sector” represents a transmitter and antenna installed at a tower. Normally, each wireless broadband service is linked to a sector. For each sector, Freeside tracks:&lt;br /&gt;
&lt;br /&gt;
* IP address&lt;br /&gt;
* Height above ground level in feet&lt;br /&gt;
* Antenna direction (azimuth and “downtilt”, i.e. elevation)&lt;br /&gt;
* Beam width (horizontal and vertical)&lt;br /&gt;
* Minimum signal margin&lt;br /&gt;
* Power output, in dBm&lt;br /&gt;
* Line loss (in cables and connectors), in dB&lt;br /&gt;
* Antenna gain, in dB&lt;br /&gt;
* “Low-quality” and “high-quality” signal margins. Note that this represents ''maximum path loss'' in dB, not signal strength as such. For example, if db_low = 100, then for “low quality” service to be available, ''P&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; + ''G&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; + ''G&amp;lt;sub&amp;gt;RX''&amp;lt;/sub&amp;gt; − ''P&amp;lt;sub&amp;gt;RX,min''&amp;lt;/sub&amp;gt; must be at least 100 dB. It is normal for db_low to be larger than db_high. Possibly these should both be export parameters rather than being set per sector. Allowing more than two levels might also be useful.&lt;br /&gt;
* “image”, a PNG graphic of the coverage map&lt;br /&gt;
* The geographic bounds of the image: west, south, east, and north.&lt;br /&gt;
* hardware_typenum, a foreign key to the hardware_type table.&lt;br /&gt;
* title, a string field for storing an external ID.&lt;br /&gt;
&lt;br /&gt;
Coverage maps are stored in “sector_coverage”. A separate record is created for each minimum signal level; the intent is that they can be used as map layers. Each one contains:&lt;br /&gt;
&lt;br /&gt;
* sectornum (tower_sector foreign key)&lt;br /&gt;
* db_loss, the level of signal loss represented by this map layer&lt;br /&gt;
* geometry, a GeoJSON polygon for the region where the signal loss is at most db_loss.&lt;br /&gt;
&lt;br /&gt;
If you move the database into PostGIS, with tower_sector.image becoming a raster and sector_coverage.geometry becoming a polygon layer, then a simple prequalification looks like:&lt;br /&gt;
&lt;br /&gt;
 dbh-&amp;gt;selectall_arrayref('SELECT sectornum, db_loss FROM sector_coverage WHERE ST_Contains(sector_coverage.geometry, ST_Point(?, ?)) AND db_loss &amp;gt; ? ORDER BY db_loss DESC', {}, $longitude, $latitude, $max_loss)&lt;br /&gt;
&lt;br /&gt;
= Tower mapping with Splat: process_generate_coverage =&lt;br /&gt;
Inserting a new tower_sector will queue a process_generate_coverage job for the sector. Editing the height, antenna position or beamwidth, or signal loss levels will also queue this job (from the replace() method). Changing a tower's coordinates will queue process_generate_coverage() for all sectors on the tower.&lt;br /&gt;
&lt;br /&gt;
CloudRF replaces most of this; supposedly they will provide an ESRI shapefile, which we can pull into OGR and convert into a stack of sector_coverage records.&lt;br /&gt;
&lt;br /&gt;
process_generate_coverage makes a temporary directory and creates a Map::Splat object, passing the tower coordinates, antenna height, azimuth and downtilt, vertical and horizontal beamwidth, and the two signal levels to calculate (db_low and db_high). It calls the calculate() method to start generating the map.&lt;br /&gt;
&lt;br /&gt;
Splat is a standalone program that operates on a set of text files. Map::Splat, our wrapper library for it, writes the input files into the temp directory:&lt;br /&gt;
&lt;br /&gt;
* The tower location file (QTH). This only contains the tower coordinates and antenna height.&lt;br /&gt;
* The model parameter file (LRP). This contains the frequency, and several static parameters (in particular, the model's safety margin may need to be adjustable).&lt;br /&gt;
* The loss contour file (LCF). This maps signal loss levels (dB) to 24-bit colors. Splat always uses white for areas with “no signal” (that is, above the highest specified loss level) and won't accept a color list containing black, or with more than 32 colors. Map::Splat accepts a list of desired signal levels, and spreads them out in the red channel from #04 to #FC. Here, we only have two signal levels, so they'll be at the endpoints.&lt;br /&gt;
* The antenna profiles in the azimuth plane (AZ) and the elevation plane (EL). These start with the antenna orientation (which is applied as a rotation to the entire data set) and then list several angular positions and field strengths (relative to the on-axis field strength). Currently we use a simple approximation where the power is 1.0 within the beamwidth, falls off by 3 dB at 1.7 * the beamwidth, and is effectively zero at twice the beamwidth. It would not be hard to modify Map::Splat to accept an .ANT file, since that's just a list of field strength at each one-degree increment.&lt;br /&gt;
&lt;br /&gt;
After that, Map::Splat ensures that we have the map data for the region of interest. It uses the data from the NASA SRTM survey, which is available from the U.S. Geological Survey. Each elevation file covers a 1 degree tile; the downloader fetches maps covering a 5 × 5 square centered on the transmitter position. (There are no map files for ocean tiles, so if the tower is near the coast, the downloader will simply fail to download some maps. Presumably there are no customers in that area, and no obstacles either.) The maps get unpacked (from zip files), converted with the “srtm2sdf” tool, and stored in /home/freeside/.splat; future Splat calculations will reuse them rather than re-downloading everything.&lt;br /&gt;
&lt;br /&gt;
There is a higher-resolution version of the SRTM data available, but it's organized on the USGS servers in a way that makes it a little more complicated to download, so we don't use it yet. If you do use it at some point, remember to change the “srtm2sdf” and “splat” command lines to “srtm2sdf-hd” and “splat-hd”.&lt;br /&gt;
&lt;br /&gt;
Map::Splat runs Splat with a command line that tells it where to find all those files, the maximum loss level to consider (equal to db_low), the maximum coverage radius, and an assumed height for each receiver (both of which should be export parameters). Splat's output includes:&lt;br /&gt;
&lt;br /&gt;
* A PPM file containing the image, which will be three colors (white and two shades of red). We pull this into ImageMagick and store it in the “image” property of the Map::Splat object.&lt;br /&gt;
* A KML file containing a “LatLonBox” element, which gives the bounding box represented by the PPM. We read this with LibXML, read the coordinates of the bounding box, and store them in the “box” property.&lt;br /&gt;
&lt;br /&gt;
Back in process_generate_coverage, the bounding box fields in tower_sector (south, west, east, north) get filled in from the Map::Splat box, and the png() method is called to convert the image to a usable form (it turns the white area transparent so that the image could be used as a ground overlay, though we don't currently do that).&lt;br /&gt;
&lt;br /&gt;
Then we call Map::Splat::polygonize_json(). The first thing this does is open the image as a GDAL raster data set. It sets the spatial reference (WGS84, GPS coordinates) and the transform matrix (so that the image pixels map onto the correct area of real geography). The output of Splat often contains a lot of noise around the edges (where the signal is close to threshold) so it applies a sieve filter that removes blobs smaller than 200 pixels, then calls GDAL's Polygonize() function to turn the red channel into an OGR in-memory polygon layer. Each area of uniform color is a polygon, tagged with the shade of red that it was.&lt;br /&gt;
&lt;br /&gt;
polygonize_json() goes through the list of known colors (we currently have two, db_low and db_high), calls SetAttributeFilter to select all polygons that color, and makes a single polygon that's the union of them. It outputs a GeoJSON feature collection, containing a feature for each signal level, marked with a “level” property. process_generate_coverage takes each of those features and writes its level and geometry (still as GeoJSON) to a sector_coverage record.&lt;br /&gt;
&lt;br /&gt;
= The tower map =&lt;br /&gt;
This exists in Freeside 4 but is greatly improved in version 5.&lt;br /&gt;
&lt;br /&gt;
search/tower-map.html is a Google Map displaying three types of features: towers, services, and coverages. Towers and services are point markers, and use a [https://developers.google.com/maps/documentation/javascript/3.exp/reference#Data.StylingFunction styling function] that merges a base style (“baseMarkerStyle”) with the “style” property of each feature, if it has one. This allows style properties (such as marker color) to be overridden per-feature.&lt;br /&gt;
&lt;br /&gt;
Point markers have click and double-click events. “clickHandler” pops up an info window connected to the feature location. If the feature has a “content” property, then that's the HTML to put in the info window. If it has a “url” property, then we make a jQuery.ajax request for that URL, and load the result into the info window. “dblClickHandler” does exactly the same thing, plus it centers the map on the marker and zooms in.&lt;br /&gt;
&lt;br /&gt;
'''Towers''' are all on a single data layer (“tower_data”) which is created within tower-map.html as the @features array. They're styled as markers with a custom “antenna” icon. The info window content is the tower's name, a link to edit/tower.html, and a list of sectors, with the number of up and down services on each sector. If there's a [#Tower exports tower export] with the export_links hook, the links will also be shown here.&lt;br /&gt;
&lt;br /&gt;
The tower info window also has checkboxes to toggle the tower's services and coverages.&lt;br /&gt;
&lt;br /&gt;
'''Services''' are in a separate data layer for each tower (“tower_svc_data[towernum]”). By default all of these layers are hidden; they're turned on from the checkbox by calling setMap(). Each layer is populated by calling [https://developers.google.com/maps/documentation/javascript/datalayer#load_geojson loadGeoJson()] on search/svc_broadband-json.cgi, passing the towernum. Each broadband service is shown as a point marker with color according to its up/down status (red, green, or gray). The info window loads dynamically from view/svc_broadband-popup.html, and shows the customer name and status (small_custview), service label, and the last known service status and latency.&lt;br /&gt;
&lt;br /&gt;
'''Coverages''' are also in a separate layer for each tower (“tower_coverage_data[towernum]”) and hidden by default. They're polygon features. These are populated, in the same way as service layers, from misc/sector_coverage-json.cgi, which pulls their geometry from sector_coverage. The coverage styling function works like the one for point markers, but sets the fill opacity based on whether the coverage feature has the “low” or “high” property. Coverage layers have no click handlers.&lt;br /&gt;
&lt;br /&gt;
Finally, there's a '''search box'''. This uses the [https://developers.google.com/maps/documentation/javascript/places-autocomplete Google Places API], is attached at the top right as a [https://developers.google.com/maps/documentation/javascript/controls#Custom_Controls map control], and lets the user search for addresses or locations near the current display area, in the usual way of Google Maps. What should happen is that clicking on a point that's not a tower or service location should query the sector_coverage table for coverage polygons containing that point, and pop up a window showing which tower/sector has the best estimated signal strength there.&lt;br /&gt;
&lt;br /&gt;
= Tower exports =&lt;br /&gt;
Currently only one of these exists (the one for TowerCoverage.com). The code from tower_sector to deal with Splat should be moved into an export also. CloudRF support should be done as yet another export.&lt;br /&gt;
&lt;br /&gt;
These aren't “exports” in the conventional sense of service-provisioning interfaces&amp;lt;nowiki&amp;gt;; &amp;lt;/nowiki&amp;gt;the export hooks are invoked from FS::tower_sector's insert(), replace(), and delete() methods. All part_exports that have “tower_sector” in their 'svc' array will be invoked. The export_insert/replace/delete hooks take the tower_sector as an argument. The insert and replace hooks are called after inserting/replacing the record (like for service exports), so if the export needs to modify the tower_sector record, it should set “local $FS::tower_sector::noexport_hack = 1” to avoid recursion.&lt;br /&gt;
&lt;br /&gt;
There's also an export_links hook, which returns optional links to be included in the tower info popup (see above). This takes the tower_sector as an argument.&lt;br /&gt;
&lt;br /&gt;
The get_antenna_types() hook was created specifically for TowerCoverage, and is used by the Edit Tower UI (specifically in elements/tr-tower_sectors.html). It should return an ordered hashref (Tie::IxHash) of hardware_type.typenum values and labels allowed in the “antenna type” property of a sector. (This is the tower antenna, not the customer's antenna.)&lt;br /&gt;
&lt;br /&gt;
Finally, there are “qual” and “qual_result” hooks for prequalifying service at a location. These both take the FS::qual object as an argument, which gives access to the proposed service location (via qual.locationnum) and sector it's trying to link to (qual.sectornum). qual() is invoked from FS::qual::insert, on whichever export was selected for qualifying service. It needs to return a hashref containing:&lt;br /&gt;
&lt;br /&gt;
* status: 'Q' for qualified, 'D' for declined. This is the most important.&lt;br /&gt;
* vendor_qual_id: A transaction number for this qualification, if there is one.&lt;br /&gt;
* options: A hashref of additional data describing the qualification. This will be inserted into FS::qual_option.&lt;br /&gt;
&lt;br /&gt;
qual_result() is called to interpret the qualification for display (probably by looking at its options). It returns a hashref containing “pkglist”, a hash of pkgparts and labels for the package definitions that could be sold at that location.&lt;br /&gt;
&lt;br /&gt;
This feature is incomplete: the UI for creating quals doesn't have a way to choose a sector, and the TowerCoverage link path implementation doesn't yet do anything useful with the results.&lt;br /&gt;
&lt;br /&gt;
== The TowerCoverage export ==&lt;br /&gt;
TowerCoverage.com provides services for both coverage mapping and link path calculation. FS::part_export::tower_towercoverage is an interface to this service.&lt;br /&gt;
&lt;br /&gt;
TowerCoverage requires the sector antenna to use a known configuration. A list of these configurations is hardcoded in the tower_towercoverage module; the first time an export of this type is created, we create a hardware class named “TowerCoverage.com antenna”, and insert the entire list into hardware_type under this class. hardware_type.title contains the TowerCoverage ID of the antenna type. The export has a get_antenna_types() method to list the allowed types.&lt;br /&gt;
&lt;br /&gt;
Certain coverage parameters need to be configured on the export rather than per sector: the client antenna properties (height, gain, and cable loss), maximum range, signal strength thresholds, and frequency band. Splat and/or CloudRF coverage mapping should store these things as export options also.&lt;br /&gt;
&lt;br /&gt;
The export_insert hook does most of the work. It converts the tower_sector properties to the form expected by the API (including metric units on everything, coercing some data types, and naming everything correctly) and creates an insert_coverage queue job. (export_replace does exactly the same thing.)&lt;br /&gt;
&lt;br /&gt;
insert_coverage runs from the queue and sends all its arguments in a POST request to the CoverageAPI endpoint. What it gets back is an XML document containing the path to the rendered map. We don't do anything with the map itself (since we're not logged into the TowerCoverage website). What we do is parse the map file name to get TowerCoverage's ID for this coverage map, and store that in tower_sector.title. export_links() then uses that ID to return a link to their map.&lt;br /&gt;
&lt;br /&gt;
qual() uses the Link Path API. It builds an argument list similar to the one for coverage mapping (where “Site1” is the tower and “Site2” is the client location), sends it to the LinkPathAPI endpoint, and stores everything that comes back in “options”. This is incomplete.&lt;br /&gt;
&lt;br /&gt;
= Station monitoring =&lt;br /&gt;
The tower map shows connected customers as green and unreachable customers as red. How does it know?&lt;br /&gt;
&lt;br /&gt;
“freeside-pingd” is a daemon that attempts to scan all IP addresses listed in svc_broadband and tower_sector records. The '''pingd-interval''' config enables this daemon and sets the polling frequency (or you can set it at the command line with “-i”.)&lt;br /&gt;
&lt;br /&gt;
The addr_status table stores the last known ping status of an address. During each scanning pass, the daemon fetches a list of all addresses that haven't been scanned in (interval) seconds (or at all). It then passes each address to a separate process via FS::Daemon::daemon_fork(), which rate-limits to a sensible number (defined here as “ten”) of child processes. Each child process runs the scan() function.&lt;br /&gt;
&lt;br /&gt;
scan() finds the addr_status record for the address, if there is one, then uses Net::Ping to do a TCP probe of the address, with a fixed 5-second timeout. It updates (or inserts) the addr_status with the ping result (up or down), latency, and timestamp. This could be improved by sending multiple pings to estimate packet loss, sending pings with payload data, keeping more than the most recent result, etc.&lt;br /&gt;
&lt;br /&gt;
After recording the ping result, the child process exits. If there are more addresses in the queue, daemon_fork() will start scanning the next one. After all addresses have been scanned, the daemon determines how long to wait before doing another scan. It calculates the expiration time (timestamp + interval) for each addr_status record and finds the lowest expiration time that's in the future. The daemon sleeps until that time, or at most (interval) seconds (since all scan records will be expired by then).&lt;br /&gt;
&lt;br /&gt;
FS::svc_IP_Mixin (services with IP addresses assigned) implements addr_status() (returns the service's addr_status record) and addr_status_color() (returns the CSS color name that goes with the status). search/svc_broadband-json.cgi uses this to set up the marker style. view/svc_broadband-popup.html additionally shows the latency and timestamp from the status record.&lt;br /&gt;
&lt;br /&gt;
[[Category:Freeside 5]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:5:Documentation:Tower_mapping&amp;diff=9640</id>
		<title>Freeside:5:Documentation:Tower mapping</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:5:Documentation:Tower_mapping&amp;diff=9640"/>
				<updated>2017-01-23T17:53:24Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This document describes the tower coverage features on the 5.x branch, which has not yet been released. The tower coverage system works, but further development is planned. Red text is for notes on future improvements.&lt;br /&gt;
&lt;br /&gt;
= Tower coverage calculation =&lt;br /&gt;
The goals of tower coverage calculation are:&lt;br /&gt;
&lt;br /&gt;
* ''Mapping'': estimating a tower's coverage area, for marketing, deployment planning, and regulatory reporting.&lt;br /&gt;
* ''Prequalification'': estimating the quality of signal available at a customer site, prior to doing an on-site survey.&lt;br /&gt;
&lt;br /&gt;
These require the same calculation: evaluating a signal strength function at a geographic point, using the tower's transmitter characteristics and a terrain map, and comparing to a minimum specified signal strength:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;{P}_{\mathit{RX},\mathit{\min }}\le {P}_{\mathit{TX}}+{G}_{\mathit{TX}}-{L}_{\mathit{path}}+{G}_{\mathit{RX}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where ''P&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; is the transmitter power, ''G&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; and ''G&amp;lt;sub&amp;gt;RX''&amp;lt;/sub&amp;gt; are the transmitter and receiver antenna gains, and ''L&amp;lt;sub&amp;gt;path''&amp;lt;/sub&amp;gt; is the path loss. For an unobstructed path length ''d ''and a frequency ''f'' , the path loss is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;{L}_{\mathit{path}}=20{\log }_{10}4\mathrm{\pi }df/c\approx 36.6\mathrm{dB}+20{\log }_{10}f-20{\log }_{10}d&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where ''f'' is in MHz and ''d'' is in miles. For a partially obstructed path, this is more complicated; note that the path is significantly wider than the physical antenna due to diffraction. Consult your radio engineer for details.&lt;br /&gt;
&lt;br /&gt;
Tower configuration is in '''Configuration / Services / Wireless broadband / Towers'''.&lt;br /&gt;
&lt;br /&gt;
= Tower/sector schema =&lt;br /&gt;
A record in “tower” represents a site where broadband equipment is installed, including:&lt;br /&gt;
&lt;br /&gt;
* Latitude and longitude&lt;br /&gt;
* Tower elevation&lt;br /&gt;
&lt;br /&gt;
A record in “tower_sector” represents a transmitter and antenna installed at a tower. Normally, each wireless broadband service is linked to a sector. For each sector, Freeside tracks:&lt;br /&gt;
&lt;br /&gt;
* IP address&lt;br /&gt;
* Height above ground level in feet&lt;br /&gt;
* Antenna direction (azimuth and “downtilt”, i.e. elevation)&lt;br /&gt;
* Beam width (horizontal and vertical)&lt;br /&gt;
* Minimum signal margin&lt;br /&gt;
* Power output, in dBm&lt;br /&gt;
* Line loss (in cables and connectors), in dB&lt;br /&gt;
* Antenna gain, in dB&lt;br /&gt;
* “Low-quality” and “high-quality” signal margins. Note that this represents ''maximum path loss'' in dB, not signal strength as such. For example, if db_low = 100, then for “low quality” service to be available, ''P&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; + ''G&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; + ''G&amp;lt;sub&amp;gt;RX''&amp;lt;/sub&amp;gt; − ''P&amp;lt;sub&amp;gt;RX,min''&amp;lt;/sub&amp;gt; must be at least 100 dB. It is normal for db_low to be larger than db_high. Possibly these should both be export parameters rather than being set per sector. Allowing more than two levels might also be useful.&lt;br /&gt;
* “image”, a PNG graphic of the coverage map&lt;br /&gt;
* The geographic bounds of the image: west, south, east, and north.&lt;br /&gt;
* hardware_typenum, a foreign key to the hardware_type table.&lt;br /&gt;
* title, a string field for storing an external ID.&lt;br /&gt;
&lt;br /&gt;
Coverage maps are stored in “sector_coverage”. A separate record is created for each minimum signal level; the intent is that they can be used as map layers. Each one contains:&lt;br /&gt;
&lt;br /&gt;
* sectornum (tower_sector foreign key)&lt;br /&gt;
* db_loss, the level of signal loss represented by this map layer&lt;br /&gt;
* geometry, a GeoJSON polygon for the region where the signal loss is at most db_loss.&lt;br /&gt;
&lt;br /&gt;
If you move the database into PostGIS, with tower_sector.image becoming a raster and sector_coverage.geometry becoming a polygon layer, then a simple prequalification looks like:&lt;br /&gt;
&lt;br /&gt;
 dbh-&amp;gt;selectall_arrayref('SELECT sectornum, db_loss FROM sector_coverage WHERE ST_Contains(sector_coverage.geometry, ST_Point(?, ?)) AND db_loss &amp;gt; ? ORDER BY db_loss DESC', {}, $longitude, $latitude, $max_loss)&lt;br /&gt;
&lt;br /&gt;
= Tower mapping with Splat: process_generate_coverage =&lt;br /&gt;
Inserting a new tower_sector will queue a process_generate_coverage job for the sector. Editing the height, antenna position or beamwidth, or signal loss levels will also queue this job (from the replace() method). Changing a tower's coordinates will queue process_generate_coverage() for all sectors on the tower.&lt;br /&gt;
&lt;br /&gt;
CloudRF replaces most of this; supposedly they will provide an ESRI shapefile, which we can pull into OGR and convert into a stack of sector_coverage records.&lt;br /&gt;
&lt;br /&gt;
process_generate_coverage makes a temporary directory and creates a Map::Splat object, passing the tower coordinates, antenna height, azimuth and downtilt, vertical and horizontal beamwidth, and the two signal levels to calculate (db_low and db_high). It calls the calculate() method to start generating the map.&lt;br /&gt;
&lt;br /&gt;
Splat is a standalone program that operates on a set of text files. Map::Splat, our wrapper library for it, writes the input files into the temp directory:&lt;br /&gt;
&lt;br /&gt;
* The tower location file (QTH). This only contains the tower coordinates and antenna height.&lt;br /&gt;
* The model parameter file (LRP). This contains the frequency, and several static parameters (in particular, the model's safety margin may need to be adjustable).&lt;br /&gt;
* The loss contour file (LCF). This maps signal loss levels (dB) to 24-bit colors. Splat always uses white for areas with “no signal” (that is, above the highest specified loss level) and won't accept a color list containing black, or with more than 32 colors. Map::Splat accepts a list of desired signal levels, and spreads them out in the red channel from #04 to #FC. Here, we only have two signal levels, so they'll be at the endpoints.&lt;br /&gt;
* The antenna profiles in the azimuth plane (AZ) and the elevation plane (EL). These start with the antenna orientation (which is applied as a rotation to the entire data set) and then list several angular positions and field strengths (relative to the on-axis field strength). Currently we use a simple approximation where the power is 1.0 within the beamwidth, falls off by 3 dB at 1.7 * the beamwidth, and is effectively zero at twice the beamwidth. It would not be hard to modify Map::Splat to accept an .ANT file, since that's just a list of field strength at each one-degree increment.&lt;br /&gt;
&lt;br /&gt;
After that, Map::Splat ensures that we have the map data for the region of interest. It uses the data from the NASA SRTM survey, which is available from the U.S. Geological Survey. Each elevation file covers a 1 degree tile; the downloader fetches maps covering a 5 × 5 square centered on the transmitter position. (There are no map files for ocean tiles, so if the tower is near the coast, the downloader will simply fail to download some maps. Presumably there are no customers in that area, and no obstacles either.) The maps get unpacked (from zip files), converted with the “srtm2sdf” tool, and stored in /home/freeside/.splat; future Splat calculations will reuse them rather than re-downloading everything.&lt;br /&gt;
&lt;br /&gt;
There is a higher-resolution version of the SRTM data available, but it's organized on the USGS servers in a way that makes it a little more complicated to download, so we don't use it yet. If you do use it at some point, remember to change the “srtm2sdf” and “splat” command lines to “srtm2sdf-hd” and “splat-hd”.&lt;br /&gt;
&lt;br /&gt;
Map::Splat runs Splat with a command line that tells it where to find all those files, the maximum loss level to consider (equal to db_low), the maximum coverage radius, and an assumed height for each receiver (both of which should be export parameters). Splat's output includes:&lt;br /&gt;
&lt;br /&gt;
* A PPM file containing the image, which will be three colors (white and two shades of red). We pull this into ImageMagick and store it in the “image” property of the Map::Splat object.&lt;br /&gt;
* A KML file containing a “LatLonBox” element, which gives the bounding box represented by the PPM. We read this with LibXML, read the coordinates of the bounding box, and store them in the “box” property.&lt;br /&gt;
&lt;br /&gt;
Back in process_generate_coverage, the bounding box fields in tower_sector (south, west, east, north) get filled in from the Map::Splat box, and the png() method is called to convert the image to a usable form (it turns the white area transparent so that the image could be used as a ground overlay, though we don't currently do that).&lt;br /&gt;
&lt;br /&gt;
Then we call Map::Splat::polygonize_json(). The first thing this does is open the image as a GDAL raster data set. It sets the spatial reference (WGS84, GPS coordinates) and the transform matrix (so that the image pixels map onto the correct area of real geography). The output of Splat often contains a lot of noise around the edges (where the signal is close to threshold) so it applies a sieve filter that removes blobs smaller than 200 pixels, then calls GDAL's Polygonize() function to turn the red channel into an OGR in-memory polygon layer. Each area of uniform color is a polygon, tagged with the shade of red that it was.&lt;br /&gt;
&lt;br /&gt;
polygonize_json() goes through the list of known colors (we currently have two, db_low and db_high), calls SetAttributeFilter to select all polygons that color, and makes a single polygon that's the union of them. It outputs a GeoJSON feature collection, containing a feature for each signal level, marked with a “level” property. process_generate_coverage takes each of those features and writes its level and geometry (still as GeoJSON) to a sector_coverage record.&lt;br /&gt;
&lt;br /&gt;
= The tower map =&lt;br /&gt;
This exists in Freeside 4 but is greatly improved in version 5.&lt;br /&gt;
&lt;br /&gt;
search/tower-map.html is a Google Map displaying three types of features: towers, services, and coverages. Towers and services are point markers, and use a [https://developers.google.com/maps/documentation/javascript/3.exp/reference#Data.StylingFunction styling function] that merges a base style (“baseMarkerStyle”) with the “style” property of each feature, if it has one. This allows style properties (such as marker color) to be overridden per-feature.&lt;br /&gt;
&lt;br /&gt;
Point markers have click and double-click events. “clickHandler” pops up an info window connected to the feature location. If the feature has a “content” property, then that's the HTML to put in the info window. If it has a “url” property, then we make a jQuery.ajax request for that URL, and load the result into the info window. “dblClickHandler” does exactly the same thing, plus it centers the map on the marker and zooms in.&lt;br /&gt;
&lt;br /&gt;
'''Towers''' are all on a single data layer (“tower_data”) which is created within tower-map.html as the @features array. They're styled as markers with a custom “antenna” icon. The info window content is the tower's name, a link to edit/tower.html, and a list of sectors, with the number of up and down services on each sector. If there's a [#Tower exports tower export] with the export_links hook, the links will also be shown here.&lt;br /&gt;
&lt;br /&gt;
The tower info window also has checkboxes to toggle the tower's services and coverages.&lt;br /&gt;
&lt;br /&gt;
'''Services''' are in a separate data layer for each tower (“tower_svc_data[towernum]”). By default all of these layers are hidden; they're turned on from the checkbox by calling setMap(). Each layer is populated by calling [https://developers.google.com/maps/documentation/javascript/datalayer#load_geojson loadGeoJson()] on search/svc_broadband-json.cgi, passing the towernum. Each broadband service is shown as a point marker with color according to its up/down status (red, green, or gray). The info window loads dynamically from view/svc_broadband-popup.html, and shows the customer name and status (small_custview), service label, and the last known service status and latency.&lt;br /&gt;
&lt;br /&gt;
'''Coverages''' are also in a separate layer for each tower (“tower_coverage_data[towernum]”) and hidden by default. They're polygon features. These are populated, in the same way as service layers, from misc/sector_coverage-json.cgi, which pulls their geometry from sector_coverage. The coverage styling function works like the one for point markers, but sets the fill opacity based on whether the coverage feature has the “low” or “high” property. Coverage layers have no click handlers.&lt;br /&gt;
&lt;br /&gt;
Finally, there's a '''search box'''. This uses the [https://developers.google.com/maps/documentation/javascript/places-autocomplete Google Places API], is attached at the top right as a [https://developers.google.com/maps/documentation/javascript/controls#Custom_Controls map control], and lets the user search for addresses or locations near the current display area, in the usual way of Google Maps. What should happen is that clicking on a point that's not a tower or service location should query the sector_coverage table for coverage polygons containing that point, and pop up a window showing which tower/sector has the best estimated signal strength there.&lt;br /&gt;
&lt;br /&gt;
= Tower exports =&lt;br /&gt;
Currently only one of these exists (the one for TowerCoverage.com). The code from tower_sector to deal with Splat should be moved into an export also. CloudRF support should be done as yet another export.&lt;br /&gt;
&lt;br /&gt;
These aren't “exports” in the conventional sense of service-provisioning interfaces&amp;lt;nowiki&amp;gt;; &amp;lt;/nowiki&amp;gt;the export hooks are invoked from FS::tower_sector's insert(), replace(), and delete() methods. All part_exports that have “tower_sector” in their 'svc' array will be invoked. The export_insert/replace/delete hooks take the tower_sector as an argument. The insert and replace hooks are called after inserting/replacing the record (like for service exports), so if the export needs to modify the tower_sector record, it should set “local $FS::tower_sector::noexport_hack = 1” to avoid recursion.&lt;br /&gt;
&lt;br /&gt;
There's also an export_links hook, which returns optional links to be included in the tower info popup (see above). This takes the tower_sector as an argument.&lt;br /&gt;
&lt;br /&gt;
The get_antenna_types() hook was created specifically for TowerCoverage, and is used by the Edit Tower UI (specifically in elements/tr-tower_sectors.html). It should return an ordered hashref (Tie::IxHash) of hardware_type.typenum values and labels allowed in the “antenna type” property of a sector. (This is the tower antenna, not the customer's antenna.)&lt;br /&gt;
&lt;br /&gt;
Finally, there are “qual” and “qual_result” hooks for prequalifying service at a location. These both take the FS::qual object as an argument, which gives access to the proposed service location (via qual.locationnum) and sector it's trying to link to (qual.sectornum). qual() is invoked from FS::qual::insert, on whichever export was selected for qualifying service. It needs to return a hashref containing:&lt;br /&gt;
&lt;br /&gt;
* status: 'Q' for qualified, 'D' for declined. This is the most important.&lt;br /&gt;
* vendor_qual_id: A transaction number for this qualification, if there is one.&lt;br /&gt;
* options: A hashref of additional data describing the qualification. This will be inserted into FS::qual_option.&lt;br /&gt;
&lt;br /&gt;
qual_result() is called to interpret the qualification for display (probably by looking at its options). It returns a hashref containing “pkglist”, a hash of pkgparts and labels for the package definitions that could be sold at that location.&lt;br /&gt;
&lt;br /&gt;
This feature is incomplete: the UI for creating quals doesn't have a way to choose a sector, and the TowerCoverage link path implementation doesn't yet do anything useful with the results.&lt;br /&gt;
&lt;br /&gt;
== The TowerCoverage export ==&lt;br /&gt;
TowerCoverage.com provides services for both coverage mapping and link path calculation. FS::part_export::tower_towercoverage is an interface to this service.&lt;br /&gt;
&lt;br /&gt;
TowerCoverage requires the sector antenna to use a known configuration. A list of these configurations is hardcoded in the tower_towercoverage module; the first time an export of this type is created, we create a hardware class named “TowerCoverage.com antenna”, and insert the entire list into hardware_type under this class. hardware_type.title contains the TowerCoverage ID of the antenna type. The export has a get_antenna_types() method to list the allowed types.&lt;br /&gt;
&lt;br /&gt;
Certain coverage parameters need to be configured on the export rather than per sector: the client antenna properties (height, gain, and cable loss), maximum range, signal strength thresholds, and frequency band. Splat and/or CloudRF coverage mapping should store these things as export options also.&lt;br /&gt;
&lt;br /&gt;
The export_insert hook does most of the work. It converts the tower_sector properties to the form expected by the API (including metric units on everything, coercing some data types, and naming everything correctly) and creates an insert_coverage queue job. (export_replace does exactly the same thing.)&lt;br /&gt;
&lt;br /&gt;
insert_coverage runs from the queue and sends all its arguments in a POST request to the CoverageAPI endpoint. What it gets back is an XML document containing the path to the rendered map. We don't do anything with the map itself (since we're not logged into the TowerCoverage website). What we do is parse the map file name to get TowerCoverage's ID for this coverage map, and store that in tower_sector.title. export_links() then uses that ID to return a link to their map.&lt;br /&gt;
&lt;br /&gt;
qual() uses the Link Path API. It builds an argument list similar to the one for coverage mapping (where “Site1” is the tower and “Site2” is the client location), sends it to the LinkPathAPI endpoint, and stores everything that comes back in “options”. This is incomplete.&lt;br /&gt;
&lt;br /&gt;
= Station monitoring =&lt;br /&gt;
The tower map shows connected customers as green and unreachable customers as red. How does it know?&lt;br /&gt;
&lt;br /&gt;
“freeside-pingd” is a daemon that attempts to scan all IP addresses listed in svc_broadband and tower_sector records. The '''pingd-interval''' config enables this daemon and sets the polling frequency (or you can set it at the command line with “-i”.)&lt;br /&gt;
&lt;br /&gt;
The addr_status table stores the last known ping status of an address. During each scanning pass, the daemon fetches a list of all addresses that haven't been scanned in (interval) seconds (or at all). It then passes each address to a separate process via FS::Daemon::daemon_fork(), which rate-limits to a sensible number (defined here as “ten”) of child processes. Each child process runs the scan() function.&lt;br /&gt;
&lt;br /&gt;
scan() finds the addr_status record for the address, if there is one, then uses Net::Ping to do a TCP probe of the address, with a fixed 5-second timeout. It updates (or inserts) the addr_status with the ping result (up or down), latency, and timestamp. This could be improved by sending multiple pings to estimate packet loss, sending pings with payload data, keeping more than the most recent result, etc.&lt;br /&gt;
&lt;br /&gt;
After recording the ping result, the child process exits. If there are more addresses in the queue, daemon_fork() will start scanning the next one. After all addresses have been scanned, the daemon determines how long to wait before doing another scan. It calculates the expiration time (timestamp + interval) for each addr_status record and finds the lowest expiration time that's in the future. The daemon sleeps until that time, or at most (interval) seconds (since all scan records will be expired by then).&lt;br /&gt;
&lt;br /&gt;
FS::svc_IP_Mixin (services with IP addresses assigned) implements addr_status() (returns the service's addr_status record) and addr_status_color() (returns the CSS color name that goes with the status). search/svc_broadband-json.cgi uses this to set up the marker style. view/svc_broadband-popup.html additionally shows the latency and timestamp from the status record.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:5:Documentation:Tower_mapping&amp;diff=9639</id>
		<title>Freeside:5:Documentation:Tower mapping</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:5:Documentation:Tower_mapping&amp;diff=9639"/>
				<updated>2017-01-23T17:51:47Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;This document describes the tower coverage features on the 5.x branch, which has not yet been released. The tower coverage system works, but further development is planned. Re...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This document describes the tower coverage features on the 5.x branch, which has not yet been released. The tower coverage system works, but further development is planned. Red text is for notes on future improvements.&lt;br /&gt;
&lt;br /&gt;
= Tower coverage calculation =&lt;br /&gt;
The goals of tower coverage calculation are:&lt;br /&gt;
&lt;br /&gt;
* ''Mapping'': estimating a tower's coverage area, for marketing, deployment planning, and regulatory reporting.&lt;br /&gt;
* ''Prequalification'': estimating the quality of signal available at a customer site, prior to doing an on-site survey.&lt;br /&gt;
&lt;br /&gt;
These require the same calculation: evaluating a signal strength function at a geographic point, using the tower's transmitter characteristics and a terrain map, and comparing to a minimum specified signal strength:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;{P}_{\mathit{RX},\mathit{\min }}\le {P}_{\mathit{TX}}+{G}_{\mathit{TX}}-{L}_{\mathit{path}}+{G}_{\mathit{RX}}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where ''P&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; is the transmitter power, ''G&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; and ''G&amp;lt;sub&amp;gt;RX''&amp;lt;/sub&amp;gt; are the transmitter and receiver antenna gains, and ''L&amp;lt;sub&amp;gt;path''&amp;lt;/sub&amp;gt; is the path loss. For an unobstructed path length ''d ''and a frequency ''f'' , the path loss is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;{L}_{\mathit{path}}=20{\log }_{10}4\mathrm{\pi }df/c\approx 36.6\mathrm{dB}+20{\log }_{10}f-20{\log }_{10}d&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where ''f'' is in MHz and ''d'' is in miles. For a partially obstructed path, this is more complicated; note that the path is significantly wider than the physical antenna due to diffraction. Consult your radio engineer for details.&lt;br /&gt;
&lt;br /&gt;
Tower configuration is in '''Configuration / Services / Wireless broadband / Towers'''.&lt;br /&gt;
&lt;br /&gt;
= Tower/sector schema =&lt;br /&gt;
A record in “tower” represents a site where broadband equipment is installed, including:&lt;br /&gt;
&lt;br /&gt;
* Latitude and longitude&lt;br /&gt;
* Tower elevation&lt;br /&gt;
&lt;br /&gt;
A record in “tower_sector” represents a transmitter and antenna installed at a tower. Normally, each wireless broadband service is linked to a sector. For each sector, Freeside tracks:&lt;br /&gt;
&lt;br /&gt;
* IP address&lt;br /&gt;
* Height above ground level in feet&lt;br /&gt;
* Antenna direction (azimuth and “downtilt”, i.e. elevation)&lt;br /&gt;
* Beam width (horizontal and vertical)&lt;br /&gt;
* Minimum signal margin&lt;br /&gt;
* Power output, in dBm&lt;br /&gt;
* Line loss (in cables and connectors), in dB&lt;br /&gt;
* Antenna gain, in dB&lt;br /&gt;
* “Low-quality” and “high-quality” signal margins. Note that this represents ''maximum path loss'' in dB, not signal strength as such. For example, if db_low = 100, then for “low quality” service to be available, ''P&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; + ''G&amp;lt;sub&amp;gt;TX''&amp;lt;/sub&amp;gt; + ''G&amp;lt;sub&amp;gt;RX''&amp;lt;/sub&amp;gt; − ''P&amp;lt;sub&amp;gt;RX,min''&amp;lt;/sub&amp;gt; must be at least 100 dB. It is normal for db_low to be larger than db_high. Possibly these should both be export parameters rather than being set per sector. Allowing more than two levels might also be useful.&lt;br /&gt;
* “image”, a PNG graphic of the coverage map&lt;br /&gt;
* The geographic bounds of the image: west, south, east, and north.&lt;br /&gt;
* hardware_typenum, a foreign key to the hardware_type table.&lt;br /&gt;
* title, a string field for storing an external ID.&lt;br /&gt;
&lt;br /&gt;
Coverage maps are stored in “sector_coverage”. A separate record is created for each minimum signal level; the intent is that they can be used as map layers. Each one contains:&lt;br /&gt;
&lt;br /&gt;
* sectornum (tower_sector foreign key)&lt;br /&gt;
* db_loss, the level of signal loss represented by this map layer&lt;br /&gt;
* geometry, a GeoJSON polygon for the region where the signal loss is at most db_loss.&lt;br /&gt;
&lt;br /&gt;
If you move the database into PostGIS, with tower_sector.image becoming a raster and sector_coverage.geometry becoming a polygon layer, then a simple prequalification looks like:&lt;br /&gt;
&lt;br /&gt;
 dbh-&amp;gt;selectall_arrayref('SELECT sectornum, db_loss FROM sector_coverage WHERE ST_Contains(sector_coverage.geometry, ST_Point(?, ?)) AND db_loss &amp;gt; ? ORDER BY db_loss DESC', $longitude, $latitude, $max_loss)&lt;br /&gt;
&lt;br /&gt;
= Tower mapping with Splat: process_generate_coverage =&lt;br /&gt;
Inserting a new tower_sector will queue a process_generate_coverage job for the sector. Editing the height, antenna position or beamwidth, or signal loss levels will also queue this job (from the replace() method). Changing a tower's coordinates will queue process_generate_coverage() for all sectors on the tower.&lt;br /&gt;
&lt;br /&gt;
CloudRF replaces most of this; supposedly they will provide an ESRI shapefile, which we can pull into OGR and convert into a stack of sector_coverage records.&lt;br /&gt;
&lt;br /&gt;
process_generate_coverage makes a temporary directory and creates a Map::Splat object, passing the tower coordinates, antenna height, azimuth and downtilt, vertical and horizontal beamwidth, and the two signal levels to calculate (db_low and db_high). It calls the calculate() method to start generating the map.&lt;br /&gt;
&lt;br /&gt;
Splat is a standalone program that operates on a set of text files. Map::Splat, our wrapper library for it, writes the input files into the temp directory:&lt;br /&gt;
&lt;br /&gt;
* The tower location file (QTH). This only contains the tower coordinates and antenna height.&lt;br /&gt;
* The model parameter file (LRP). This contains the frequency, and several static parameters (in particular, the model's safety margin may need to be adjustable).&lt;br /&gt;
* The loss contour file (LCF). This maps signal loss levels (dB) to 24-bit colors. Splat always uses white for areas with “no signal” (that is, above the highest specified loss level) and won't accept a color list containing black, or with more than 32 colors. Map::Splat accepts a list of desired signal levels, and spreads them out in the red channel from #04 to #FC. Here, we only have two signal levels, so they'll be at the endpoints.&lt;br /&gt;
* The antenna profiles in the azimuth plane (AZ) and the elevation plane (EL). These start with the antenna orientation (which is applied as a rotation to the entire data set) and then list several angular positions and field strengths (relative to the on-axis field strength). Currently we use a simple approximation where the power is 1.0 within the beamwidth, falls off by 3 dB at 1.7 * the beamwidth, and is effectively zero at twice the beamwidth. It would not be hard to modify Map::Splat to accept an .ANT file, since that's just a list of field strength at each one-degree increment.&lt;br /&gt;
&lt;br /&gt;
After that, Map::Splat ensures that we have the map data for the region of interest. It uses the data from the NASA SRTM survey, which is available from the U.S. Geological Survey. Each elevation file covers a 1 degree tile; the downloader fetches maps covering a 5 × 5 square centered on the transmitter position. (There are no map files for ocean tiles, so if the tower is near the coast, the downloader will simply fail to download some maps. Presumably there are no customers in that area, and no obstacles either.) The maps get unpacked (from zip files), converted with the “srtm2sdf” tool, and stored in /home/freeside/.splat; future Splat calculations will reuse them rather than re-downloading everything.&lt;br /&gt;
&lt;br /&gt;
There is a higher-resolution version of the SRTM data available, but it's organized on the USGS servers in a way that makes it a little more complicated to download, so we don't use it yet. If you do use it at some point, remember to change the “srtm2sdf” and “splat” command lines to “srtm2sdf-hd” and “splat-hd”.&lt;br /&gt;
&lt;br /&gt;
Map::Splat runs Splat with a command line that tells it where to find all those files, the maximum loss level to consider (equal to db_low), the maximum coverage radius, and an assumed height for each receiver (both of which should be export parameters). Splat's output includes:&lt;br /&gt;
&lt;br /&gt;
* A PPM file containing the image, which will be three colors (white and two shades of red). We pull this into ImageMagick and store it in the “image” property of the Map::Splat object.&lt;br /&gt;
* A KML file containing a “LatLonBox” element, which gives the bounding box represented by the PPM. We read this with LibXML, read the coordinates of the bounding box, and store them in the “box” property.&lt;br /&gt;
&lt;br /&gt;
Back in process_generate_coverage, the bounding box fields in tower_sector (south, west, east, north) get filled in from the Map::Splat box, and the png() method is called to convert the image to a usable form (it turns the white area transparent so that the image could be used as a ground overlay, though we don't currently do that).&lt;br /&gt;
&lt;br /&gt;
Then we call Map::Splat::polygonize_json(). The first thing this does is open the image as a GDAL raster data set. It sets the spatial reference (WGS84, GPS coordinates) and the transform matrix (so that the image pixels map onto the correct area of real geography). The output of Splat often contains a lot of noise around the edges (where the signal is close to threshold) so it applies a sieve filter that removes blobs smaller than 200 pixels, then calls GDAL's Polygonize() function to turn the red channel into an OGR in-memory polygon layer. Each area of uniform color is a polygon, tagged with the shade of red that it was.&lt;br /&gt;
&lt;br /&gt;
polygonize_json() goes through the list of known colors (we currently have two, db_low and db_high), calls SetAttributeFilter to select all polygons that color, and makes a single polygon that's the union of them. It outputs a GeoJSON feature collection, containing a feature for each signal level, marked with a “level” property. process_generate_coverage takes each of those features and writes its level and geometry (still as GeoJSON) to a sector_coverage record.&lt;br /&gt;
&lt;br /&gt;
= The tower map =&lt;br /&gt;
This exists in Freeside 4 but is greatly improved in version 5.&lt;br /&gt;
&lt;br /&gt;
search/tower-map.html is a Google Map displaying three types of features: towers, services, and coverages. Towers and services are point markers, and use a [https://developers.google.com/maps/documentation/javascript/3.exp/reference#Data.StylingFunction styling function] that merges a base style (“baseMarkerStyle”) with the “style” property of each feature, if it has one. This allows style properties (such as marker color) to be overridden per-feature.&lt;br /&gt;
&lt;br /&gt;
Point markers have click and double-click events. “clickHandler” pops up an info window connected to the feature location. If the feature has a “content” property, then that's the HTML to put in the info window. If it has a “url” property, then we make a jQuery.ajax request for that URL, and load the result into the info window. “dblClickHandler” does exactly the same thing, plus it centers the map on the marker and zooms in.&lt;br /&gt;
&lt;br /&gt;
'''Towers''' are all on a single data layer (“tower_data”) which is created within tower-map.html as the @features array. They're styled as markers with a custom “antenna” icon. The info window content is the tower's name, a link to edit/tower.html, and a list of sectors, with the number of up and down services on each sector. If there's a [#Tower exports tower export] with the export_links hook, the links will also be shown here.&lt;br /&gt;
&lt;br /&gt;
The tower info window also has checkboxes to toggle the tower's services and coverages.&lt;br /&gt;
&lt;br /&gt;
'''Services''' are in a separate data layer for each tower (“tower_svc_data[towernum]”). By default all of these layers are hidden; they're turned on from the checkbox by calling setMap(). Each layer is populated by calling [https://developers.google.com/maps/documentation/javascript/datalayer#load_geojson loadGeoJson()] on search/svc_broadband-json.cgi, passing the towernum. Each broadband service is shown as a point marker with color according to its up/down status (red, green, or gray). The info window loads dynamically from view/svc_broadband-popup.html, and shows the customer name and status (small_custview), service label, and the last known service status and latency.&lt;br /&gt;
&lt;br /&gt;
'''Coverages''' are also in a separate layer for each tower (“tower_coverage_data[towernum]”) and hidden by default. They're polygon features. These are populated, in the same way as service layers, from misc/sector_coverage-json.cgi, which pulls their geometry from sector_coverage. The coverage styling function works like the one for point markers, but sets the fill opacity based on whether the coverage feature has the “low” or “high” property. Coverage layers have no click handlers.&lt;br /&gt;
&lt;br /&gt;
Finally, there's a '''search box'''. This uses the [https://developers.google.com/maps/documentation/javascript/places-autocomplete Google Places API], is attached at the top right as a [https://developers.google.com/maps/documentation/javascript/controls#Custom_Controls map control], and lets the user search for addresses or locations near the current display area, in the usual way of Google Maps. What should happen is that clicking on a point that's not a tower or service location should query the sector_coverage table for coverage polygons containing that point, and pop up a window showing which tower/sector has the best estimated signal strength there.&lt;br /&gt;
&lt;br /&gt;
= Tower exports =&lt;br /&gt;
Currently only one of these exists (the one for TowerCoverage.com). The code from tower_sector to deal with Splat should be moved into an export also. CloudRF support should be done as yet another export.&lt;br /&gt;
&lt;br /&gt;
These aren't “exports” in the conventional sense of service-provisioning interfaces&amp;lt;nowiki&amp;gt;; &amp;lt;/nowiki&amp;gt;the export hooks are invoked from FS::tower_sector's insert(), replace(), and delete() methods. All part_exports that have “tower_sector” in their 'svc' array will be invoked. The export_insert/replace/delete hooks take the tower_sector as an argument. The insert and replace hooks are called after inserting/replacing the record (like for service exports), so if the export needs to modify the tower_sector record, it should set “local $FS::tower_sector::noexport_hack = 1” to avoid recursion.&lt;br /&gt;
&lt;br /&gt;
There's also an export_links hook, which returns optional links to be included in the tower info popup (see above). This takes the tower_sector as an argument.&lt;br /&gt;
&lt;br /&gt;
The get_antenna_types() hook was created specifically for TowerCoverage, and is used by the Edit Tower UI (specifically in elements/tr-tower_sectors.html). It should return an ordered hashref (Tie::IxHash) of hardware_type.typenum values and labels allowed in the “antenna type” property of a sector. (This is the tower antenna, not the customer's antenna.)&lt;br /&gt;
&lt;br /&gt;
Finally, there are “qual” and “qual_result” hooks for prequalifying service at a location. These both take the FS::qual object as an argument, which gives access to the proposed service location (via qual.locationnum) and sector it's trying to link to (qual.sectornum). qual() is invoked from FS::qual::insert, on whichever export was selected for qualifying service. It needs to return a hashref containing:&lt;br /&gt;
&lt;br /&gt;
* status: 'Q' for qualified, 'D' for declined. This is the most important.&lt;br /&gt;
* vendor_qual_id: A transaction number for this qualification, if there is one.&lt;br /&gt;
* options: A hashref of additional data describing the qualification. This will be inserted into FS::qual_option.&lt;br /&gt;
&lt;br /&gt;
qual_result() is called to interpret the qualification for display (probably by looking at its options). It returns a hashref containing “pkglist”, a hash of pkgparts and labels for the package definitions that could be sold at that location.&lt;br /&gt;
&lt;br /&gt;
This feature is incomplete: the UI for creating quals doesn't have a way to choose a sector, and the TowerCoverage link path implementation doesn't yet do anything useful with the results.&lt;br /&gt;
&lt;br /&gt;
== The TowerCoverage export ==&lt;br /&gt;
TowerCoverage.com provides services for both coverage mapping and link path calculation. FS::part_export::tower_towercoverage is an interface to this service.&lt;br /&gt;
&lt;br /&gt;
TowerCoverage requires the sector antenna to use a known configuration. A list of these configurations is hardcoded in the tower_towercoverage module; the first time an export of this type is created, we create a hardware class named “TowerCoverage.com antenna”, and insert the entire list into hardware_type under this class. hardware_type.title contains the TowerCoverage ID of the antenna type. The export has a get_antenna_types() method to list the allowed types.&lt;br /&gt;
&lt;br /&gt;
Certain coverage parameters need to be configured on the export rather than per sector: the client antenna properties (height, gain, and cable loss), maximum range, signal strength thresholds, and frequency band. Splat and/or CloudRF coverage mapping should store these things as export options also.&lt;br /&gt;
&lt;br /&gt;
The export_insert hook does most of the work. It converts the tower_sector properties to the form expected by the API (including metric units on everything, coercing some data types, and naming everything correctly) and creates an insert_coverage queue job. (export_replace does exactly the same thing.)&lt;br /&gt;
&lt;br /&gt;
insert_coverage runs from the queue and sends all its arguments in a POST request to the CoverageAPI endpoint. What it gets back is an XML document containing the path to the rendered map. We don't do anything with the map itself (since we're not logged into the TowerCoverage website). What we do is parse the map file name to get TowerCoverage's ID for this coverage map, and store that in tower_sector.title. export_links() then uses that ID to return a link to their map.&lt;br /&gt;
&lt;br /&gt;
qual() uses the Link Path API. It builds an argument list similar to the one for coverage mapping (where “Site1” is the tower and “Site2” is the client location), sends it to the LinkPathAPI endpoint, and stores everything that comes back in “options”. This is incomplete.&lt;br /&gt;
&lt;br /&gt;
= Station monitoring =&lt;br /&gt;
The tower map shows connected customers as green and unreachable customers as red. How does it know?&lt;br /&gt;
&lt;br /&gt;
“freeside-pingd” is a daemon that attempts to scan all IP addresses listed in svc_broadband and tower_sector records. The '''pingd-interval''' config enables this daemon and sets the polling frequency (or you can set it at the command line with “-i”.)&lt;br /&gt;
&lt;br /&gt;
The addr_status table stores the last known ping status of an address. During each scanning pass, the daemon fetches a list of all addresses that haven't been scanned in (interval) seconds (or at all). It then passes each address to a separate process via FS::Daemon::daemon_fork(), which rate-limits to a sensible number (defined here as “ten”) of child processes. Each child process runs the scan() function.&lt;br /&gt;
&lt;br /&gt;
scan() finds the addr_status record for the address, if there is one, then uses Net::Ping to do a TCP probe of the address, with a fixed 5-second timeout. It updates (or inserts) the addr_status with the ping result (up or down), latency, and timestamp. This could be improved by sending multiple pings to estimate packet loss, sending pings with payload data, keeping more than the most recent result, etc.&lt;br /&gt;
&lt;br /&gt;
After recording the ping result, the child process exits. If there are more addresses in the queue, daemon_fork() will start scanning the next one. After all addresses have been scanned, the daemon determines how long to wait before doing another scan. It calculates the expiration time (timestamp + interval) for each addr_status record and finds the lowest expiration time that's in the future. The daemon sleeps until that time, or at most (interval) seconds (since all scan records will be expired by then).&lt;br /&gt;
&lt;br /&gt;
FS::svc_IP_Mixin (services with IP addresses assigned) implements addr_status() (returns the service's addr_status record) and addr_status_color() (returns the CSS color name that goes with the status). search/svc_broadband-json.cgi uses this to set up the marker style. view/svc_broadband-popup.html additionally shows the latency and timestamp from the status record.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9638</id>
		<title>Freeside:4:Documentation:AFC taxes</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9638"/>
				<updated>2017-01-16T21:38:09Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Avalara currently has two tax services: the one we call &amp;quot;avalara&amp;quot;, which is a general sales-tax services, and the one we call &amp;quot;billsoft&amp;quot; and they call &amp;quot;Avalara for Communications&amp;quot; (AFC).&lt;br /&gt;
&lt;br /&gt;
AFC has a batch-oriented FTP interface that it inherited from Billsoft. This is what we support right now. It also has a nice REST interface which is somewhat experimental. Support for this is planned in the future.&lt;br /&gt;
&lt;br /&gt;
= Setup =&lt;br /&gt;
&lt;br /&gt;
Set the '''tax_data_vendor''' config to &amp;quot;billsoft&amp;quot;, and the '''billsoft-company-code''' config to your three-letter company code.&lt;br /&gt;
&lt;br /&gt;
Create an upload target (Configuration / with hostname ftp.billsoft.com, and the login and password provided by Avalara.&lt;br /&gt;
&lt;br /&gt;
Import the data files. In '''Tools / Importing / Tax rates''', select &amp;quot;Billsoft PCodes&amp;quot; and import the &amp;quot;all_adr.txt&amp;quot; file. Then select &amp;quot;Tax products&amp;quot; and import the &amp;quot;transervdesc.txt&amp;quot; file.&lt;br /&gt;
&lt;br /&gt;
Run freeside-tax-location-update. This will assign PCodes for all existing package locations that don't yet have one, based on their zip codes.&lt;br /&gt;
&lt;br /&gt;
Assign a tax product to each package definition. See below about this.&lt;br /&gt;
&lt;br /&gt;
== Setting up tax products ==&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;tax product&amp;quot; is a code assigned to a sale to identify what kind of product it is for tax purposes. For example, &amp;quot;recurring charge for local phone service&amp;quot; is a tax product. Tax products are listed in part_pkg_taxproduct. In the UI, entering a string into the tax product field will open an autocomplete search for tax products containing that string.&lt;br /&gt;
&lt;br /&gt;
A package definition (part_pkg) has a single &amp;quot;taxproductnum&amp;quot; field for the base tax product for the package. In the simple case where a package has only one-time or only recurring charges, and no CDR usage charges, this is the only tax product it needs.&lt;br /&gt;
[[File:Editing_afc_taxproduct_1.png]]&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;By usage class&amp;quot; button allows tax products to be set separately for setup, recurring non-usage, and usage by class. The taxproductnums are stored in part_pkg options named &amp;quot;usage_taxproduct_FOO&amp;quot; where FOO is the word 'setup' or 'recur' or an integer (a usage_class.classnum).&lt;br /&gt;
&lt;br /&gt;
See the AFC Telecom Mapping Guidelines and other manuals for details on selecting tax products. For usage-billed phone service, AFC has separate tax product groups (T-codes) for interstate, intrastate, and local services. Local services are typically billed as &amp;quot;Local Exchange charge&amp;quot; (7:5); long-distance charges will be intrastate (2:1), interstate (1:1), or international (1:12) toll charges. Other local exchange charges such as FCC subcriber line / LNP recovery are their own categories. This is all different if you're using VoIP or cellular service.&lt;br /&gt;
&lt;br /&gt;
[[File:Editing_afc_taxproduct_2.png]]&lt;br /&gt;
&lt;br /&gt;
(Local service also has a separate, parallel set of tax products ending in the word &amp;quot;Bundle&amp;quot;. This is for service plans that don't break out the local and long-distance charge on the bill; for complicated reasons it's exempt from certain taxes. &lt;br /&gt;
&lt;br /&gt;
There should probably be a switch to turn off sending all the CDRs to AFC and just give them a total amount billed in each usage class.&lt;br /&gt;
&lt;br /&gt;
AFC requires that packages that provide &amp;quot;phone lines&amp;quot; of whatever type declare those. Usually this is the &amp;quot;Lines&amp;quot; tax product (such as local voice lines, 7:21, or VoIP lines, 19:21) but there are other options such as &amp;quot;PBX trunks&amp;quot; and even &amp;quot;extensions&amp;quot; (which would require more flexibility in counting units than we currently have). The &amp;quot;Per-line tax product&amp;quot; in the package definition sets the part_pkg.units_taxproductnum to this field.&lt;br /&gt;
&lt;br /&gt;
Finally, there are &amp;quot;Invoice&amp;quot; tax products (always S-code 43). These do not need to be configured. If ''any'' tax product on the invoice has a T-code for which there's an Invoice tax product then an additional line will be submitted declaring the Invoice. For example, if an invoice includes a charge for local phone service (7:5) and one for VoIP access charges (19:6) then it will be sent with both a 7:43 and a 19:43, declaring it as both a local phone invoice and a VoIP invoice.&lt;br /&gt;
&lt;br /&gt;
= Batch workflow =&lt;br /&gt;
&lt;br /&gt;
freeside-daily runs FS::Cron::tax_batch::process_tax_batch. If the tax engine has the 'batch' flag (which only AFC has, probably ever), it will call transfer_batch() on the tax engine.&lt;br /&gt;
&lt;br /&gt;
Note that if AFC is active and a customer has pending invoices, bill_and_collect will NOT run collect() for that customer (i.e. it won't run post-billing events, such as charging credit cards or mailing out invoices). We'll come back to that.&lt;br /&gt;
&lt;br /&gt;
transfer_batch() calls create_batch() to write the batch into a file in $cache_dir/Billsoft/upload. These files follow a strict naming scheme: company code + %Y%m%d + two-letter sequential ID starting with 'AA'. So on Jan 12 2017, the first batch would be FIS20170112AA.CSV, the second would be FIS20170112AB.CSV, etc. Normally you should only need one batch per day, but if you somehow send another one, transfer_batch will detect that there's already one named *AA.CSV (in $cache_dir/Billsoft/upload) and name the next one AB, and so on.&lt;br /&gt;
&lt;br /&gt;
create_batch() finds all pending invoices and writes transaction lines into the batch. See the next section for details.&lt;br /&gt;
&lt;br /&gt;
It then compresses the CSV file into &amp;quot;FTP.ZIP&amp;quot; and uploads that, then polls the server waiting for a result batch. Currently this has a timeout of one day (the $TIMEOUT variable); that's probably longer than it should be.&lt;br /&gt;
&lt;br /&gt;
The result batch will be named the same thing as the uploaded CSV file, but .ZIP, and with &amp;quot;R&amp;quot; after the company code (so &amp;quot;FIS'''R'''20170112AA.ZIP&amp;quot;). It will be saved into $cache_dir/Billsoft/download. It contains several files describing many stages of processing, but the one we want is the detail report, ending in _dtl.rpt.csv. This is a CSV file containing one line for each tax on each transaction, which is conveniently the same data structure as our allocation table.&lt;br /&gt;
&lt;br /&gt;
batch_import() reads this file line by line and creates a tax line item for each distinct tax name. It will also create tax_rate_location, tax_class, and tax_rate records as necessary to describe the taxes that are being charged.&lt;br /&gt;
&lt;br /&gt;
After processing the file, batch_import() removes the pending flags from all invoices, then queues a job to run FS::cust_main::Billing::collect on each customer who had an invoice processed this way.&lt;br /&gt;
&lt;br /&gt;
= What we send them =&lt;br /&gt;
&lt;br /&gt;
AFC tax products have two parts, a transaction code (for the kind of service that's being sold, like local telephone, VoIP, etc.) and a service code (for the kind of charge that this is, like monthly subscription, toll, installation, etc.). I internally call these &amp;quot;T-codes&amp;quot; and &amp;quot;S-codes&amp;quot;. See the various AFC manuals (Local Exchange Mapping, VoIP Mapping, etc.) for details.&lt;br /&gt;
&lt;br /&gt;
AFC expects each possibly-taxable feature of the sale to be declared as a separate transaction line. So for each cust_bill_pkg record on the invoice, we send the following transaction lines:&lt;br /&gt;
* Each recurring fee (cust_bill_pkg.recur).&lt;br /&gt;
* Each non-recurring fee (cust_bill_pkg.setup).&lt;br /&gt;
* The number of phone lines (for per-line surcharges).&lt;br /&gt;
* Each CDR (for voip_cdr packages). Yes, this is a large number of records.&lt;br /&gt;
&lt;br /&gt;
In almost all cases, per-line or per-customer) charges such as E911 fees need to be applied once per month, so the package must have a monthly billing cycle.&lt;br /&gt;
&lt;br /&gt;
Fields applying to the whole invoice ('''%bill_properties'''):&lt;br /&gt;
* RequestType: Always 'CalcTaxes' for now.&lt;br /&gt;
* BillTo... (CountryISO, PCode, ZipCode, ZipP4): the customer's billing address.&lt;br /&gt;
* Date: The invoice date (YYYYMMDD).&lt;br /&gt;
* CustomerType: The customer's tax status ('B'usiness, 'R'esidential, etc.).&lt;br /&gt;
* InvoiceNumber: Freeside invnum (echoed in the result file, used to find this invoice again).&lt;br /&gt;
* CustomerNumber: Freeside custnum (echoed in the result file, used for error checking).&lt;br /&gt;
* and some sitewide flags describing the service provider's status:&lt;br /&gt;
** Facilities: Indicates the provider owns the physical infrastructure used to deliver services.&lt;br /&gt;
** Franchise: Indicates that the provider sells service pursuant to a franchise agreement with the jurisdiction.&lt;br /&gt;
** Regulated: Indicates that the provider is a regulated public utility.&lt;br /&gt;
** BusinessClass: Indicates whether the provider is classified as a CLEC or ILEC.&lt;br /&gt;
&lt;br /&gt;
Fields applying to all line items of a specific package ('''%pkg_properties'''):&lt;br /&gt;
* Optional: Freeside billpkgnum (echoed in the result file; the field is just a generic place for a numeric identifier).&lt;br /&gt;
* Sale: Whether this package is sold on a wholesale basis (a part_pkg flag, currently not available in any package).&lt;br /&gt;
&lt;br /&gt;
Fields sent with recurring fee or setup fee lines:&lt;br /&gt;
* Termination... (CountryISO, PCode, ZipCode, ZipP4): the package's service address.&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the recurring or setup fee.&lt;br /&gt;
* Charge: The amount of the sale.&lt;br /&gt;
&lt;br /&gt;
Fields sent with CDR lines:&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the CDR's usage class.&lt;br /&gt;
* Charge: The rated charge for the call.&lt;br /&gt;
* Minutes: The call duration in minutes (including fraction).&lt;br /&gt;
* OriginationNpaNxx: The source number's prefix.&lt;br /&gt;
* TerminationNpaNxx: The destination number's prefix.&lt;br /&gt;
&lt;br /&gt;
Note that if LRN lookup is enabled, the Origination/Termination prefixes will be taken from the resolved LRN numbers.&lt;br /&gt;
&lt;br /&gt;
Fields sent with the &amp;quot;number of phone lines&amp;quot; transaction line:&lt;br /&gt;
* Charges: Zero.&lt;br /&gt;
* Lines: The number of phone services, from FS::part_pkg::calc_units. This transaction line will be sent only if it's greater than zero.&lt;br /&gt;
* TransactionType/ServiceType: The tax product selected with part_pkg.units_taxproductnum. Usually this will be S-code 21 (&amp;quot;Lines&amp;quot;) but can be different for certain local exchange services.&lt;br /&gt;
&lt;br /&gt;
For each T-code that was present on the invoice, we'll also send a &amp;quot;per invoice&amp;quot; transaction line with S-code 43. The &amp;quot;Lines&amp;quot; and &amp;quot;Charges&amp;quot; are both zero on this line.&lt;br /&gt;
&lt;br /&gt;
= Processing the result file =&lt;br /&gt;
&lt;br /&gt;
batch_import() reads records line by line and looks at several columns:&lt;br /&gt;
* InvoiceNumber and Optional columns contain invnum and billpkgnum. We use these to relate the tax back to a taxable line item.&lt;br /&gt;
* TaxTypeID (number) and TaxType (string) are the &amp;quot;kind&amp;quot; of tax, like &amp;quot;Sales Tax&amp;quot; or &amp;quot;Universal Service&amp;quot;. We create a tax_class record with these values if there isn't one already.&lt;br /&gt;
* CountryISO, State, County, Locality, and PCode identify the tax locality. PCodes are unique and we store them as tax_rate_location.geocode. If there isn't a record with this PCode already, we create one.&lt;br /&gt;
* TaxLevelID is the taxing authority: national, state/province, county, city, or unincorporated district. We use this to decide which tax_rate_location field to use in the tax description (for example, &amp;quot;CA Sales Tax&amp;quot; vs. &amp;quot;Los Angeles County Sales Tax&amp;quot;).&lt;br /&gt;
* TaxAmount is the amount of tax, in dollars.&lt;br /&gt;
&lt;br /&gt;
batch_import() constructs the tax name from the geographic name of the taxing authority (like &amp;quot;US&amp;quot; or &amp;quot;NV&amp;quot; or &amp;quot;Las Vegas&amp;quot;) and the TaxType string (like &amp;quot;Sales Tax&amp;quot;) and finds or creates a tax_rate record with that name, and geocode = the PCode of the locality.&lt;br /&gt;
&lt;br /&gt;
It then calls add_tax_item(), an internal method that creates a cust_bill_pkg record with the correct itemdesc (= the tax name). If there's already one (from another line item subject to the same-named tax) then add_tax_item increments the amount of that item.&lt;br /&gt;
&lt;br /&gt;
Finally, it creates a cust_bill_pkg_tax_rate_location allocation record linking the tax and taxed line items, the tax_rate and tax_rate_location records, and the amount.&lt;br /&gt;
&lt;br /&gt;
If there are errors processing the batch, we log them and continue. The invoice will remain in the pending state and be resubmitted in the next batch.&lt;br /&gt;
&lt;br /&gt;
All pending invoices should be cleared at the end of daily processing. There should be a report / notification if any remain in the system after that, but there currently isn't.&lt;br /&gt;
&lt;br /&gt;
[[Category:Freeside 4]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9637</id>
		<title>Freeside:4:Documentation:AFC taxes</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9637"/>
				<updated>2017-01-16T21:31:04Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Avalara currently has two tax services: the one we call &amp;quot;avalara&amp;quot;, which is a general sales-tax services, and the one we call &amp;quot;billsoft&amp;quot; and they call &amp;quot;Avalara for Communications&amp;quot; (AFC).&lt;br /&gt;
&lt;br /&gt;
AFC has a batch-oriented FTP interface that it inherited from Billsoft. This is what we support right now. It also has a nice REST interface which is somewhat experimental. Support for this is planned in the future.&lt;br /&gt;
&lt;br /&gt;
= Setup =&lt;br /&gt;
&lt;br /&gt;
Set the '''tax_data_vendor''' config to &amp;quot;billsoft&amp;quot;, and the '''billsoft-company-code''' config to your three-letter company code.&lt;br /&gt;
&lt;br /&gt;
Create an upload target (Configuration / with hostname ftp.billsoft.com, and the login and password provided by Avalara.&lt;br /&gt;
&lt;br /&gt;
Import the data files. In '''Tools / Importing / Tax rates''', select &amp;quot;Billsoft PCodes&amp;quot; and import the &amp;quot;all_adr.txt&amp;quot; file. Then select &amp;quot;Tax products&amp;quot; and import the &amp;quot;transervdesc.txt&amp;quot; file.&lt;br /&gt;
&lt;br /&gt;
Run freeside-tax-location-update. This will assign PCodes for all existing package locations that don't yet have one, based on their zip codes.&lt;br /&gt;
&lt;br /&gt;
Assign a tax product to each package definition. See below about this.&lt;br /&gt;
&lt;br /&gt;
== Setting up tax products ==&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;tax product&amp;quot; is a code assigned to a sale to identify what kind of product it is for tax purposes. For example, &amp;quot;recurring charge for local phone service&amp;quot; is a tax product. Tax products are listed in part_pkg_taxproduct. In the UI, entering a string into the tax product field will open an autocomplete search for tax products containing that string.&lt;br /&gt;
&lt;br /&gt;
A package definition (part_pkg) has a single &amp;quot;taxproductnum&amp;quot; field for the base tax product for the package. In the simple case where a package has only one-time or only recurring charges, and no CDR usage charges, this is the only tax product it needs.&lt;br /&gt;
[[File:Editing_afc_taxproduct_1.png]]&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;By usage class&amp;quot; button allows tax products to be set separately for setup, recurring non-usage, and usage by class. The taxproductnums are stored in part_pkg options named &amp;quot;usage_taxproduct_FOO&amp;quot; where FOO is the word 'setup' or 'recur' or an integer (a usage_class.classnum).&lt;br /&gt;
&lt;br /&gt;
See the AFC Telecom Mapping Guidelines and other manuals for details on selecting tax products. For usage-billed phone service, AFC has separate tax product groups (T-codes) for interstate, intrastate, and local services. Local services are typically billed as &amp;quot;Local Exchange charge&amp;quot; (7:5); long-distance charges will be intrastate (2:1), interstate (1:1), or international (1:12) toll charges. Other local exchange charges such as FCC subcriber line / LNP recovery are their own categories. This is all different if you're using VoIP or cellular service.&lt;br /&gt;
[[File:Editing_afc_taxproduct_2.png]]&lt;br /&gt;
&lt;br /&gt;
(Local service also has a separate, parallel set of tax products ending in the word &amp;quot;Bundle&amp;quot;. This is for service plans that don't break out the local and long-distance charge on the bill; for complicated reasons it's exempt from certain taxes. &lt;br /&gt;
&lt;br /&gt;
There should probably be a switch to turn off sending all the CDRs to AFC and just give them a total amount billed in each usage class.&lt;br /&gt;
&lt;br /&gt;
AFC requires that packages that provide &amp;quot;phone lines&amp;quot; of whatever type declare those. Usually this is the &amp;quot;Lines&amp;quot; tax product (such as local voice lines, 7:21, or VoIP lines, 19:21) but there are other options such as &amp;quot;PBX trunks&amp;quot; and even &amp;quot;extensions&amp;quot; (which would require more flexibility in counting units than we currently have). The &amp;quot;Per-line tax product&amp;quot; in the package definition sets the part_pkg.units_taxproductnum to this field.&lt;br /&gt;
&lt;br /&gt;
Finally, there are &amp;quot;Invoice&amp;quot; tax products (always S-code 43). These do not need to be configured. If ''any'' tax product on the invoice has a T-code for which there's an Invoice tax product then an additional line will be submitted declaring the Invoice. For example, if an invoice includes a charge for local phone service (7:5) and one for VoIP access charges (19:6) then it will be sent with both a 7:43 and a 19:43, declaring it as both a local phone invoice and a VoIP invoice.&lt;br /&gt;
&lt;br /&gt;
= Batch workflow =&lt;br /&gt;
&lt;br /&gt;
freeside-daily runs FS::Cron::tax_batch::process_tax_batch. If the tax engine has the 'batch' flag (which only AFC has, probably ever), it will call transfer_batch() on the tax engine.&lt;br /&gt;
&lt;br /&gt;
Note that if AFC is active and a customer has pending invoices, bill_and_collect will NOT run collect() for that customer (i.e. it won't run post-billing events, such as charging credit cards or mailing out invoices). We'll come back to that.&lt;br /&gt;
&lt;br /&gt;
transfer_batch() calls create_batch() to write the batch into a file in $cache_dir/Billsoft/upload. These files follow a strict naming scheme: company code + %Y%m%d + two-letter sequential ID starting with 'AA'. So on Jan 12 2017, the first batch would be FIS20170112AA.CSV, the second would be FIS20170112AB.CSV, etc. Normally you should only need one batch per day, but if you somehow send another one, transfer_batch will detect that there's already one named *AA.CSV (in $cache_dir/Billsoft/upload) and name the next one AB, and so on.&lt;br /&gt;
&lt;br /&gt;
create_batch() finds all pending invoices and writes transaction lines into the batch. See the next section for details.&lt;br /&gt;
&lt;br /&gt;
It then compresses the CSV file into &amp;quot;FTP.ZIP&amp;quot; and uploads that, then polls the server waiting for a result batch. Currently this has a timeout of one day (the $TIMEOUT variable); that's probably longer than it should be.&lt;br /&gt;
&lt;br /&gt;
The result batch will be named the same thing as the uploaded CSV file, but .ZIP, and with &amp;quot;R&amp;quot; after the company code (so &amp;quot;FIS'''R'''20170112AA.ZIP&amp;quot;). It will be saved into $cache_dir/Billsoft/download. It contains several files describing many stages of processing, but the one we want is the detail report, ending in _dtl.rpt.csv. This is a CSV file containing one line for each tax on each transaction, which is conveniently the same data structure as our allocation table.&lt;br /&gt;
&lt;br /&gt;
batch_import() reads this file line by line and creates a tax line item for each distinct tax name. It will also create tax_rate_location, tax_class, and tax_rate records as necessary to describe the taxes that are being charged.&lt;br /&gt;
&lt;br /&gt;
After processing the file, batch_import() removes the pending flags from all invoices, then queues a job to run FS::cust_main::Billing::collect on each customer who had an invoice processed this way.&lt;br /&gt;
&lt;br /&gt;
= What we send them =&lt;br /&gt;
&lt;br /&gt;
AFC tax products have two parts, a transaction code (for the kind of service that's being sold, like local telephone, VoIP, etc.) and a service code (for the kind of charge that this is, like monthly subscription, toll, installation, etc.). I internally call these &amp;quot;T-codes&amp;quot; and &amp;quot;S-codes&amp;quot;. See the various AFC manuals (Local Exchange Mapping, VoIP Mapping, etc.) for details.&lt;br /&gt;
&lt;br /&gt;
AFC expects each possibly-taxable feature of the sale to be declared as a separate transaction line. So for each cust_bill_pkg record on the invoice, we send the following transaction lines:&lt;br /&gt;
* Each recurring fee (cust_bill_pkg.recur).&lt;br /&gt;
* Each non-recurring fee (cust_bill_pkg.setup).&lt;br /&gt;
* The number of phone lines (for per-line surcharges).&lt;br /&gt;
* Each CDR (for voip_cdr packages). Yes, this is a large number of records.&lt;br /&gt;
&lt;br /&gt;
In almost all cases, per-line or per-customer) charges such as E911 fees need to be applied once per month, so the package must have a monthly billing cycle.&lt;br /&gt;
&lt;br /&gt;
Fields applying to the whole invoice ('''%bill_properties'''):&lt;br /&gt;
* RequestType: Always 'CalcTaxes' for now.&lt;br /&gt;
* BillTo... (CountryISO, PCode, ZipCode, ZipP4): the customer's billing address.&lt;br /&gt;
* Date: The invoice date (YYYYMMDD).&lt;br /&gt;
* CustomerType: The customer's tax status ('B'usiness, 'R'esidential, etc.).&lt;br /&gt;
* InvoiceNumber: Freeside invnum (echoed in the result file, used to find this invoice again).&lt;br /&gt;
* CustomerNumber: Freeside custnum (echoed in the result file, used for error checking).&lt;br /&gt;
* and some sitewide flags describing the service provider's status:&lt;br /&gt;
** Facilities: Indicates the provider owns the physical infrastructure used to deliver services.&lt;br /&gt;
** Franchise: Indicates that the provider sells service pursuant to a franchise agreement with the jurisdiction.&lt;br /&gt;
** Regulated: Indicates that the provider is a regulated public utility.&lt;br /&gt;
** BusinessClass: Indicates whether the provider is classified as a CLEC or ILEC.&lt;br /&gt;
&lt;br /&gt;
Fields applying to all line items of a specific package ('''%pkg_properties'''):&lt;br /&gt;
* Optional: Freeside billpkgnum (echoed in the result file; the field is just a generic place for a numeric identifier).&lt;br /&gt;
* Sale: Whether this package is sold on a wholesale basis (a part_pkg flag, currently not available in any package).&lt;br /&gt;
&lt;br /&gt;
Fields sent with recurring fee or setup fee lines:&lt;br /&gt;
* Termination... (CountryISO, PCode, ZipCode, ZipP4): the package's service address.&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the recurring or setup fee.&lt;br /&gt;
* Charge: The amount of the sale.&lt;br /&gt;
&lt;br /&gt;
Fields sent with CDR lines:&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the CDR's usage class.&lt;br /&gt;
* Charge: The rated charge for the call.&lt;br /&gt;
* Minutes: The call duration in minutes (including fraction).&lt;br /&gt;
* OriginationNpaNxx: The source number's prefix.&lt;br /&gt;
* TerminationNpaNxx: The destination number's prefix.&lt;br /&gt;
&lt;br /&gt;
Note that if LRN lookup is enabled, the Origination/Termination prefixes will be taken from the resolved LRN numbers.&lt;br /&gt;
&lt;br /&gt;
Fields sent with the &amp;quot;number of phone lines&amp;quot; transaction line:&lt;br /&gt;
* Charges: Zero.&lt;br /&gt;
* Lines: The number of phone services, from FS::part_pkg::calc_units. This transaction line will be sent only if it's greater than zero.&lt;br /&gt;
* TransactionType/ServiceType: The tax product selected with part_pkg.units_taxproductnum. Usually this will be S-code 21 (&amp;quot;Lines&amp;quot;) but can be different for certain local exchange services.&lt;br /&gt;
&lt;br /&gt;
For each T-code that was present on the invoice, we'll also send a &amp;quot;per invoice&amp;quot; transaction line with S-code 43. The &amp;quot;Lines&amp;quot; and &amp;quot;Charges&amp;quot; are both zero on this line.&lt;br /&gt;
&lt;br /&gt;
= Processing the result file =&lt;br /&gt;
&lt;br /&gt;
batch_import() reads records line by line and looks at several columns:&lt;br /&gt;
* InvoiceNumber and Optional columns contain invnum and billpkgnum. We use these to relate the tax back to a taxable line item.&lt;br /&gt;
* TaxTypeID (number) and TaxType (string) are the &amp;quot;kind&amp;quot; of tax, like &amp;quot;Sales Tax&amp;quot; or &amp;quot;Universal Service&amp;quot;. We create a tax_class record with these values if there isn't one already.&lt;br /&gt;
* CountryISO, State, County, Locality, and PCode identify the tax locality. PCodes are unique and we store them as tax_rate_location.geocode. If there isn't a record with this PCode already, we create one.&lt;br /&gt;
* TaxLevelID is the taxing authority: national, state/province, county, city, or unincorporated district. We use this to decide which tax_rate_location field to use in the tax description (for example, &amp;quot;CA Sales Tax&amp;quot; vs. &amp;quot;Los Angeles County Sales Tax&amp;quot;).&lt;br /&gt;
* TaxAmount is the amount of tax, in dollars.&lt;br /&gt;
&lt;br /&gt;
batch_import() constructs the tax name from the geographic name of the taxing authority (like &amp;quot;US&amp;quot; or &amp;quot;NV&amp;quot; or &amp;quot;Las Vegas&amp;quot;) and the TaxType string (like &amp;quot;Sales Tax&amp;quot;) and finds or creates a tax_rate record with that name, and geocode = the PCode of the locality.&lt;br /&gt;
&lt;br /&gt;
It then calls add_tax_item(), an internal method that creates a cust_bill_pkg record with the correct itemdesc (= the tax name). If there's already one (from another line item subject to the same-named tax) then add_tax_item increments the amount of that item.&lt;br /&gt;
&lt;br /&gt;
Finally, it creates a cust_bill_pkg_tax_rate_location allocation record linking the tax and taxed line items, the tax_rate and tax_rate_location records, and the amount.&lt;br /&gt;
&lt;br /&gt;
If there are errors processing the batch, we log them and continue. The invoice will remain in the pending state and be resubmitted in the next batch.&lt;br /&gt;
&lt;br /&gt;
All pending invoices should be cleared at the end of daily processing. There should be a report / notification if any remain in the system after that, but there currently isn't.&lt;br /&gt;
&lt;br /&gt;
[[Category:Freeside 4]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:TaxEngine&amp;diff=9636</id>
		<title>Freeside:4:Documentation:TaxEngine</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:TaxEngine&amp;diff=9636"/>
				<updated>2017-01-16T21:30:16Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tax Engines =&lt;br /&gt;
&lt;br /&gt;
Freeside 4 refactors the code for tax calculation into &amp;quot;tax engines&amp;quot;. This document describes the interface to use a tax engine for tax calculation, or to implement a new tax calculation method.&lt;br /&gt;
&lt;br /&gt;
A tax engine is a method for calculating tax on invoices. Before version 4, Freeside supported two methods:&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;Internal&amp;quot; tax, using the ''cust_main_county'' table to define tax rates manually. Tax elements on each line item are stored in ''cust_bill_pkg_tax_location'' and linked to both ''cust_main_county'' and ''cust_location''.&lt;br /&gt;
* &amp;quot;External&amp;quot; tax using tax tables downloaded from CCH. The name is misleading, as Freeside performs the tax calculations internally using tables stored in the Freeside database.&lt;br /&gt;
&lt;br /&gt;
Starting with version 4, &amp;quot;External&amp;quot; is renamed to &amp;quot;CCH&amp;quot;, and the following new methods are available:&lt;br /&gt;
* [http://www.avalara.com Avalara]&lt;br /&gt;
* [[Freeside:4:Documentation:AFC_taxes|Avalara for Communications, AKA Billsoft]]&lt;br /&gt;
* [http://www.suretax.com SureTax]&lt;br /&gt;
&lt;br /&gt;
== External interface ==&lt;br /&gt;
&lt;br /&gt;
The ''enable_taxproducts'' config sets the tax engine to use on the system. FS::TaxEngine-&amp;gt;new will return an object of that type.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;normal&amp;quot; workflow is:&lt;br /&gt;
&lt;br /&gt;
* As each line item is created, call ''add_sale'', passing that line item. Not all tax engines use this.&lt;br /&gt;
* Insert the invoice and its line items.&lt;br /&gt;
* Call ''calculate_taxes'', passing the FS::cust_bill object.&lt;br /&gt;
** This calls ''make_taxlines'' to create a list of tax elements.&lt;br /&gt;
** It then calls ''consolidate_taxlines'' to group the elements by tax name and create a list of tax line items (FS::cust_bill_pkg objects).&lt;br /&gt;
** ''calculate_taxes'' will return that list, with each line item linked to an array of tax elements.&lt;br /&gt;
* Insert the tax line items.&lt;br /&gt;
* Add the total tax to the invoice's &amp;quot;charged&amp;quot; field, and set the &amp;quot;pending&amp;quot; field to null.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
FS::TaxEngine objects also implement a few peripheral methods:&lt;br /&gt;
&lt;br /&gt;
* ''info'': Returns some metadata about the tax engine, such as which tables it uses for tax rates and elements, whether it requires batch submission of invoices, allows manual location selection, etc.&lt;br /&gt;
* ''cust_tax_locations'': Takes an FS::cust_location object, returns a list of tax jurisdictions that it could possibly be in. Only used if manual location selection is allowed.&lt;br /&gt;
* ''add_taxproduct'': A &amp;quot;taxproduct&amp;quot; (FS::part_pkg_taxproduct) is a category of taxable products. Some tax systems predefine these and provide a large list. Others expect the user to define them; in that case, this method is called when the user tries to manually add a taxproduct to the system.&lt;br /&gt;
&lt;br /&gt;
Finally, each tax engine can provide a UI component (under browse/part_pkg_taxproduct/) to select the taxproduct when editing a package definition. This is necessary because the part_pkg_taxproduct table is used in different ways by different engines. See examples in that directory.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
'''You must implement either ''make_taxlines'' or ''calculate_taxes''.''' Everything else is optional.&lt;br /&gt;
&lt;br /&gt;
If your calculation method produces pre-consolidated line items (that is, one line for each distinct tax name), it should be implemented as ''calculate_taxes''. This takes an FS::cust_bill object and returns a list of FS::cust_bill_pkg objects for the line items. Each of these should have tax elements (either FS::cust_bill_pkg_tax_location or FS::cust_bill_pkg_tax_rate_location) that reference the &amp;quot;billpkgnum&amp;quot; fields of the line items tax was charged on.&lt;br /&gt;
&lt;br /&gt;
If your calculation method just returns a list of taxes for each taxed line item, it should be implemented as ''make_taxlines''. This also takes an FS::cust_bill object, and returns a list of tax elements. The tax line items will be created from those. In this case, the ''info'' hash will need to include ''link_table'', the name of the table where you store tax elements (e.g. cust_bill_pkg_tax_location).&lt;br /&gt;
&lt;br /&gt;
[[Category:Freeside 4]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Category:Freeside_4&amp;diff=9635</id>
		<title>Category:Freeside 4</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Category:Freeside_4&amp;diff=9635"/>
				<updated>2017-01-16T21:29:58Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;Pages related to Freeside version 4.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pages related to Freeside version 4.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:TaxEngine&amp;diff=9634</id>
		<title>Freeside:4:Documentation:TaxEngine</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:TaxEngine&amp;diff=9634"/>
				<updated>2017-01-16T21:29:23Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tax Engines =&lt;br /&gt;
&lt;br /&gt;
Freeside 4 refactors the code for tax calculation into &amp;quot;tax engines&amp;quot;. This document describes the interface to use a tax engine for tax calculation, or to implement a new tax calculation method.&lt;br /&gt;
&lt;br /&gt;
A tax engine is a method for calculating tax on invoices. Before version 4, Freeside supported two methods:&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;Internal&amp;quot; tax, using the ''cust_main_county'' table to define tax rates manually. Tax elements on each line item are stored in ''cust_bill_pkg_tax_location'' and linked to both ''cust_main_county'' and ''cust_location''.&lt;br /&gt;
* &amp;quot;External&amp;quot; tax using tax tables downloaded from CCH. The name is misleading, as Freeside performs the tax calculations internally using tables stored in the Freeside database.&lt;br /&gt;
&lt;br /&gt;
Starting with version 4, &amp;quot;External&amp;quot; is renamed to &amp;quot;CCH&amp;quot;, and the following new methods are available:&lt;br /&gt;
* [http://www.avalara.com Avalara]&lt;br /&gt;
* [[Freeside:4:Documentation:AFC_taxes|Avalara for Communications, AKA Billsoft]]&lt;br /&gt;
* [http://www.suretax.com SureTax]&lt;br /&gt;
&lt;br /&gt;
== External interface ==&lt;br /&gt;
&lt;br /&gt;
The ''enable_taxproducts'' config sets the tax engine to use on the system. FS::TaxEngine-&amp;gt;new will return an object of that type.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;normal&amp;quot; workflow is:&lt;br /&gt;
&lt;br /&gt;
* As each line item is created, call ''add_sale'', passing that line item. Not all tax engines use this.&lt;br /&gt;
* Insert the invoice and its line items.&lt;br /&gt;
* Call ''calculate_taxes'', passing the FS::cust_bill object.&lt;br /&gt;
** This calls ''make_taxlines'' to create a list of tax elements.&lt;br /&gt;
** It then calls ''consolidate_taxlines'' to group the elements by tax name and create a list of tax line items (FS::cust_bill_pkg objects).&lt;br /&gt;
** ''calculate_taxes'' will return that list, with each line item linked to an array of tax elements.&lt;br /&gt;
* Insert the tax line items.&lt;br /&gt;
* Add the total tax to the invoice's &amp;quot;charged&amp;quot; field, and set the &amp;quot;pending&amp;quot; field to null.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
FS::TaxEngine objects also implement a few peripheral methods:&lt;br /&gt;
&lt;br /&gt;
* ''info'': Returns some metadata about the tax engine, such as which tables it uses for tax rates and elements, whether it requires batch submission of invoices, allows manual location selection, etc.&lt;br /&gt;
* ''cust_tax_locations'': Takes an FS::cust_location object, returns a list of tax jurisdictions that it could possibly be in. Only used if manual location selection is allowed.&lt;br /&gt;
* ''add_taxproduct'': A &amp;quot;taxproduct&amp;quot; (FS::part_pkg_taxproduct) is a category of taxable products. Some tax systems predefine these and provide a large list. Others expect the user to define them; in that case, this method is called when the user tries to manually add a taxproduct to the system.&lt;br /&gt;
&lt;br /&gt;
Finally, each tax engine can provide a UI component (under browse/part_pkg_taxproduct/) to select the taxproduct when editing a package definition. This is necessary because the part_pkg_taxproduct table is used in different ways by different engines. See examples in that directory.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
'''You must implement either ''make_taxlines'' or ''calculate_taxes''.''' Everything else is optional.&lt;br /&gt;
&lt;br /&gt;
If your calculation method produces pre-consolidated line items (that is, one line for each distinct tax name), it should be implemented as ''calculate_taxes''. This takes an FS::cust_bill object and returns a list of FS::cust_bill_pkg objects for the line items. Each of these should have tax elements (either FS::cust_bill_pkg_tax_location or FS::cust_bill_pkg_tax_rate_location) that reference the &amp;quot;billpkgnum&amp;quot; fields of the line items tax was charged on.&lt;br /&gt;
&lt;br /&gt;
If your calculation method just returns a list of taxes for each taxed line item, it should be implemented as ''make_taxlines''. This also takes an FS::cust_bill object, and returns a list of tax elements. The tax line items will be created from those. In this case, the ''info'' hash will need to include ''link_table'', the name of the table where you store tax elements (e.g. cust_bill_pkg_tax_location).&lt;br /&gt;
&lt;br /&gt;
[[Category:Freeside 4]] [[Category:Taxes]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9633</id>
		<title>Freeside:4:Documentation:AFC taxes</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9633"/>
				<updated>2017-01-16T21:12:53Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: clarify things&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Avalara currently has two tax services: the one we call &amp;quot;avalara&amp;quot;, which is a general sales-tax services, and the one we call &amp;quot;billsoft&amp;quot; and they call &amp;quot;Avalara for Communications&amp;quot; (AFC).&lt;br /&gt;
&lt;br /&gt;
AFC has a batch-oriented FTP interface that it inherited from Billsoft. This is what we support right now. It also has a nice REST interface which is somewhat experimental. Support for this is planned in the future.&lt;br /&gt;
&lt;br /&gt;
= Setup =&lt;br /&gt;
&lt;br /&gt;
Set the '''tax_data_vendor''' config to &amp;quot;billsoft&amp;quot;, and the '''billsoft-company-code''' config to your three-letter company code.&lt;br /&gt;
&lt;br /&gt;
Create an upload target (Configuration / with hostname ftp.billsoft.com, and the login and password provided by Avalara.&lt;br /&gt;
&lt;br /&gt;
Import the data files. In '''Tools / Importing / Tax rates''', select &amp;quot;Billsoft PCodes&amp;quot; and import the &amp;quot;all_adr.txt&amp;quot; file. Then select &amp;quot;Tax products&amp;quot; and import the &amp;quot;transervdesc.txt&amp;quot; file.&lt;br /&gt;
&lt;br /&gt;
Run freeside-tax-location-update. This will assign PCodes for all existing package locations that don't yet have one, based on their zip codes.&lt;br /&gt;
&lt;br /&gt;
Assign a tax product to each package definition. See below about this.&lt;br /&gt;
&lt;br /&gt;
== Setting up tax products ==&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;tax product&amp;quot; is a code assigned to a sale to identify what kind of product it is for tax purposes. For example, &amp;quot;recurring charge for local phone service&amp;quot; is a tax product. Tax products are listed in part_pkg_taxproduct. In the UI, entering a string into the tax product field will open an autocomplete search for tax products containing that string.&lt;br /&gt;
&lt;br /&gt;
A package definition (part_pkg) has a single &amp;quot;taxproductnum&amp;quot; field for the base tax product for the package. In the simple case where a package has only one-time or only recurring charges, and no CDR usage charges, this is the only tax product it needs.&lt;br /&gt;
[[File:Editing_afc_taxproduct_1.png]]&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;By usage class&amp;quot; button allows tax products to be set separately for setup, recurring non-usage, and usage by class. The taxproductnums are stored in part_pkg options named &amp;quot;usage_taxproduct_FOO&amp;quot; where FOO is the word 'setup' or 'recur' or an integer (a usage_class.classnum).&lt;br /&gt;
&lt;br /&gt;
See the AFC Telecom Mapping Guidelines and other manuals for details on selecting tax products. For usage-billed phone service, AFC has separate tax product groups (T-codes) for interstate, intrastate, and local services. Local services are typically billed as &amp;quot;Local Exchange charge&amp;quot; (7:5); long-distance charges will be intrastate (2:1), interstate (1:1), or international (1:12) toll charges. Other local exchange charges such as FCC subcriber line / LNP recovery are their own categories. This is all different if you're using VoIP or cellular service.&lt;br /&gt;
[[File:Editing_afc_taxproduct_2.png]]&lt;br /&gt;
&lt;br /&gt;
(Local service also has a separate, parallel set of tax products ending in the word &amp;quot;Bundle&amp;quot;. This is for service plans that don't break out the local and long-distance charge on the bill; for complicated reasons it's exempt from certain taxes. &lt;br /&gt;
&lt;br /&gt;
There should probably be a switch to turn off sending all the CDRs to AFC and just give them a total amount billed in each usage class.&lt;br /&gt;
&lt;br /&gt;
AFC requires that packages that provide &amp;quot;phone lines&amp;quot; of whatever type declare those. Usually this is the &amp;quot;Lines&amp;quot; tax product (such as local voice lines, 7:21, or VoIP lines, 19:21) but there are other options such as &amp;quot;PBX trunks&amp;quot; and even &amp;quot;extensions&amp;quot; (which would require more flexibility in counting units than we currently have). The &amp;quot;Per-line tax product&amp;quot; in the package definition sets the part_pkg.units_taxproductnum to this field.&lt;br /&gt;
&lt;br /&gt;
Finally, there are &amp;quot;Invoice&amp;quot; tax products (always S-code 43). These do not need to be configured. If ''any'' tax product on the invoice has a T-code for which there's an Invoice tax product then an additional line will be submitted declaring the Invoice. For example, if an invoice includes a charge for local phone service (7:5) and one for VoIP access charges (19:6) then it will be sent with both a 7:43 and a 19:43, declaring it as both a local phone invoice and a VoIP invoice.&lt;br /&gt;
&lt;br /&gt;
= Batch workflow =&lt;br /&gt;
&lt;br /&gt;
freeside-daily runs FS::Cron::tax_batch::process_tax_batch. If the tax engine has the 'batch' flag (which only AFC has, probably ever), it will call transfer_batch() on the tax engine.&lt;br /&gt;
&lt;br /&gt;
Note that if AFC is active and a customer has pending invoices, bill_and_collect will NOT run collect() for that customer (i.e. it won't run post-billing events, such as charging credit cards or mailing out invoices). We'll come back to that.&lt;br /&gt;
&lt;br /&gt;
transfer_batch() calls create_batch() to write the batch into a file in $cache_dir/Billsoft/upload. These files follow a strict naming scheme: company code + %Y%m%d + two-letter sequential ID starting with 'AA'. So on Jan 12 2017, the first batch would be FIS20170112AA.CSV, the second would be FIS20170112AB.CSV, etc. Normally you should only need one batch per day, but if you somehow send another one, transfer_batch will detect that there's already one named *AA.CSV (in $cache_dir/Billsoft/upload) and name the next one AB, and so on.&lt;br /&gt;
&lt;br /&gt;
create_batch() finds all pending invoices and writes transaction lines into the batch. See the next section for details.&lt;br /&gt;
&lt;br /&gt;
It then compresses the CSV file into &amp;quot;FTP.ZIP&amp;quot; and uploads that, then polls the server waiting for a result batch. Currently this has a timeout of one day (the $TIMEOUT variable); that's probably longer than it should be.&lt;br /&gt;
&lt;br /&gt;
The result batch will be named the same thing as the uploaded CSV file, but .ZIP, and with &amp;quot;R&amp;quot; after the company code (so &amp;quot;FIS'''R'''20170112AA.ZIP&amp;quot;). It will be saved into $cache_dir/Billsoft/download. It contains several files describing many stages of processing, but the one we want is the detail report, ending in _dtl.rpt.csv. This is a CSV file containing one line for each tax on each transaction, which is conveniently the same data structure as our allocation table.&lt;br /&gt;
&lt;br /&gt;
batch_import() reads this file line by line and creates a tax line item for each distinct tax name. It will also create tax_rate_location, tax_class, and tax_rate records as necessary to describe the taxes that are being charged.&lt;br /&gt;
&lt;br /&gt;
After processing the file, batch_import() removes the pending flags from all invoices, then queues a job to run FS::cust_main::Billing::collect on each customer who had an invoice processed this way.&lt;br /&gt;
&lt;br /&gt;
= What we send them =&lt;br /&gt;
&lt;br /&gt;
AFC tax products have two parts, a transaction code (for the kind of service that's being sold, like local telephone, VoIP, etc.) and a service code (for the kind of charge that this is, like monthly subscription, toll, installation, etc.). I internally call these &amp;quot;T-codes&amp;quot; and &amp;quot;S-codes&amp;quot;. See the various AFC manuals (Local Exchange Mapping, VoIP Mapping, etc.) for details.&lt;br /&gt;
&lt;br /&gt;
AFC expects each possibly-taxable feature of the sale to be declared as a separate transaction line. So for each cust_bill_pkg record on the invoice, we send the following transaction lines:&lt;br /&gt;
* Each recurring fee (cust_bill_pkg.recur).&lt;br /&gt;
* Each non-recurring fee (cust_bill_pkg.setup).&lt;br /&gt;
* The number of phone lines (for per-line surcharges).&lt;br /&gt;
* Each CDR (for voip_cdr packages). Yes, this is a large number of records.&lt;br /&gt;
&lt;br /&gt;
In almost all cases, per-line or per-customer) charges such as E911 fees need to be applied once per month, so the package must have a monthly billing cycle.&lt;br /&gt;
&lt;br /&gt;
Fields applying to the whole invoice ('''%bill_properties'''):&lt;br /&gt;
* RequestType: Always 'CalcTaxes' for now.&lt;br /&gt;
* BillTo... (CountryISO, PCode, ZipCode, ZipP4): the customer's billing address.&lt;br /&gt;
* Date: The invoice date (YYYYMMDD).&lt;br /&gt;
* CustomerType: The customer's tax status ('B'usiness, 'R'esidential, etc.).&lt;br /&gt;
* InvoiceNumber: Freeside invnum (echoed in the result file, used to find this invoice again).&lt;br /&gt;
* CustomerNumber: Freeside custnum (echoed in the result file, used for error checking).&lt;br /&gt;
* and some sitewide flags describing the service provider's status:&lt;br /&gt;
** Facilities: Indicates the provider owns the physical infrastructure used to deliver services.&lt;br /&gt;
** Franchise: Indicates that the provider sells service pursuant to a franchise agreement with the jurisdiction.&lt;br /&gt;
** Regulated: Indicates that the provider is a regulated public utility.&lt;br /&gt;
** BusinessClass: Indicates whether the provider is classified as a CLEC or ILEC.&lt;br /&gt;
&lt;br /&gt;
Fields applying to all line items of a specific package ('''%pkg_properties'''):&lt;br /&gt;
* Optional: Freeside billpkgnum (echoed in the result file; the field is just a generic place for a numeric identifier).&lt;br /&gt;
* Sale: Whether this package is sold on a wholesale basis (a part_pkg flag, currently not available in any package).&lt;br /&gt;
&lt;br /&gt;
Fields sent with recurring fee or setup fee lines:&lt;br /&gt;
* Termination... (CountryISO, PCode, ZipCode, ZipP4): the package's service address.&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the recurring or setup fee.&lt;br /&gt;
* Charge: The amount of the sale.&lt;br /&gt;
&lt;br /&gt;
Fields sent with CDR lines:&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the CDR's usage class.&lt;br /&gt;
* Charge: The rated charge for the call.&lt;br /&gt;
* Minutes: The call duration in minutes (including fraction).&lt;br /&gt;
* OriginationNpaNxx: The source number's prefix.&lt;br /&gt;
* TerminationNpaNxx: The destination number's prefix.&lt;br /&gt;
&lt;br /&gt;
Note that if LRN lookup is enabled, the Origination/Termination prefixes will be taken from the resolved LRN numbers.&lt;br /&gt;
&lt;br /&gt;
Fields sent with the &amp;quot;number of phone lines&amp;quot; transaction line:&lt;br /&gt;
* Charges: Zero.&lt;br /&gt;
* Lines: The number of phone services, from FS::part_pkg::calc_units. This transaction line will be sent only if it's greater than zero.&lt;br /&gt;
* TransactionType/ServiceType: The tax product selected with part_pkg.units_taxproductnum. Usually this will be S-code 21 (&amp;quot;Lines&amp;quot;) but can be different for certain local exchange services.&lt;br /&gt;
&lt;br /&gt;
For each T-code that was present on the invoice, we'll also send a &amp;quot;per invoice&amp;quot; transaction line with S-code 43. The &amp;quot;Lines&amp;quot; and &amp;quot;Charges&amp;quot; are both zero on this line.&lt;br /&gt;
&lt;br /&gt;
= Processing the result file =&lt;br /&gt;
&lt;br /&gt;
batch_import() reads records line by line and looks at several columns:&lt;br /&gt;
* InvoiceNumber and Optional columns contain invnum and billpkgnum. We use these to relate the tax back to a taxable line item.&lt;br /&gt;
* TaxTypeID (number) and TaxType (string) are the &amp;quot;kind&amp;quot; of tax, like &amp;quot;Sales Tax&amp;quot; or &amp;quot;Universal Service&amp;quot;. We create a tax_class record with these values if there isn't one already.&lt;br /&gt;
* CountryISO, State, County, Locality, and PCode identify the tax locality. PCodes are unique and we store them as tax_rate_location.geocode. If there isn't a record with this PCode already, we create one.&lt;br /&gt;
* TaxLevelID is the taxing authority: national, state/province, county, city, or unincorporated district. We use this to decide which tax_rate_location field to use in the tax description (for example, &amp;quot;CA Sales Tax&amp;quot; vs. &amp;quot;Los Angeles County Sales Tax&amp;quot;).&lt;br /&gt;
* TaxAmount is the amount of tax, in dollars.&lt;br /&gt;
&lt;br /&gt;
batch_import() constructs the tax name from the geographic name of the taxing authority (like &amp;quot;US&amp;quot; or &amp;quot;NV&amp;quot; or &amp;quot;Las Vegas&amp;quot;) and the TaxType string (like &amp;quot;Sales Tax&amp;quot;) and finds or creates a tax_rate record with that name, and geocode = the PCode of the locality.&lt;br /&gt;
&lt;br /&gt;
It then calls add_tax_item(), an internal method that creates a cust_bill_pkg record with the correct itemdesc (= the tax name). If there's already one (from another line item subject to the same-named tax) then add_tax_item increments the amount of that item.&lt;br /&gt;
&lt;br /&gt;
Finally, it creates a cust_bill_pkg_tax_rate_location allocation record linking the tax and taxed line items, the tax_rate and tax_rate_location records, and the amount.&lt;br /&gt;
&lt;br /&gt;
If there are errors processing the batch, we log them and continue. The invoice will remain in the pending state and be resubmitted in the next batch.&lt;br /&gt;
&lt;br /&gt;
All pending invoices should be cleared at the end of daily processing. There should be a report / notification if any remain in the system after that, but there currently isn't.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9632</id>
		<title>Freeside:4:Documentation:AFC taxes</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9632"/>
				<updated>2017-01-16T21:07:02Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Avalara currently has two tax services: the one we call &amp;quot;avalara&amp;quot;, which is a general sales-tax services, and the one we call &amp;quot;billsoft&amp;quot; and they call &amp;quot;Avalara for Communications&amp;quot; (AFC).&lt;br /&gt;
&lt;br /&gt;
AFC has a batch-oriented FTP interface that it inherited from Billsoft. This is what we support right now. It also has a nice REST interface which is somewhat experimental. Support for this is planned in the future.&lt;br /&gt;
&lt;br /&gt;
= Setup =&lt;br /&gt;
&lt;br /&gt;
Set the '''tax_data_vendor''' config to &amp;quot;billsoft&amp;quot;, and the '''billsoft-company-code''' config to your three-letter company code.&lt;br /&gt;
&lt;br /&gt;
Create an upload target (Configuration / with hostname ftp.billsoft.com, and the login and password provided by Avalara.&lt;br /&gt;
&lt;br /&gt;
Import the data files. In '''Tools / Importing / Tax rates''', select &amp;quot;Billsoft PCodes&amp;quot; and import the &amp;quot;all_adr.txt&amp;quot; file. Then select &amp;quot;Tax products&amp;quot; and import the &amp;quot;transervdesc.txt&amp;quot; file.&lt;br /&gt;
&lt;br /&gt;
Run freeside-tax-location-update. This will assign PCodes for all existing package locations that don't yet have one, based on their zip codes.&lt;br /&gt;
&lt;br /&gt;
Assign a tax product to each package definition. See below about this.&lt;br /&gt;
&lt;br /&gt;
== Setting up tax products ==&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;tax product&amp;quot; is a code assigned to a sale to identify what kind of product it is for tax purposes. For example, &amp;quot;recurring charge for local phone service&amp;quot; is a tax product. Tax products are listed in part_pkg_taxproduct.&lt;br /&gt;
&lt;br /&gt;
A package definition (part_pkg) has a single &amp;quot;taxproductnum&amp;quot; field for the base tax product for the package. In the simple case where a package has only one-time or only recurring charges, and no CDR usage charges, this is the only tax product it needs.&lt;br /&gt;
[[File:Editing_afc_taxproduct_1.png]]&lt;br /&gt;
&lt;br /&gt;
More specifically, tax products can be set using part_pkg options named &amp;quot;usage_taxproduct_FOO&amp;quot; where FOO is the word 'setup' or 'recur' or an integer (a usage_class.classnum). The package will then be broken down into setup, recurring non-usage, and usage (separate by usage class), which are taxed separately. See the AFC Telecom Mapping Guidelines and other manuals for details.&lt;br /&gt;
&lt;br /&gt;
For usage-billed phone service, AFC has separate tax product groups (T-codes) for interstate, intrastate, and local services. Local services are typically billed as &amp;quot;Local Exchange charge&amp;quot; (7:5); long-distance charges will be intrastate (2:1), interstate (1:1), or international (1:12) toll charges. Other local exchange charges such as FCC subcriber line / LNP recovery are their own categories. This is all different if you're using VoIP or cellular service.&lt;br /&gt;
[[File:Editing_afc_taxproduct_2.png]]&lt;br /&gt;
&lt;br /&gt;
(Local service also has a separate, parallel set of tax products ending in the word &amp;quot;Bundle&amp;quot;. This is for service plans that don't break out the local and long-distance charge on the bill; for complicated reasons it's exempt from certain taxes. &lt;br /&gt;
&lt;br /&gt;
There should probably be a switch to turn off sending all the CDRs to AFC and just give them a total amount billed in each usage class.&lt;br /&gt;
&lt;br /&gt;
AFC requires that packages that provide &amp;quot;phone lines&amp;quot; of whatever type declare those. Usually this is the &amp;quot;Lines&amp;quot; taxproduct (such as local voice lines, 7:21, or VoIP lines, 19:21) but there are other options such as &amp;quot;PBX trunks&amp;quot; and even &amp;quot;extensions&amp;quot; (which would require more flexibility in counting units than we currently have). The &amp;quot;Per-line tax product&amp;quot; in the package definition sets the part_pkg.units_taxproductnum to this field.&lt;br /&gt;
&lt;br /&gt;
Finally, there are &amp;quot;Invoice&amp;quot; tax products (always S-code 43). These do not need to be configured. If ''any'' tax product on the invoice has a T-code for which there's an Invoice tax product then an additional line will be submitted declaring the Invoice. For example, if an invoice includes a charge for local phone service (7:5) and one for VoIP access charges (19:6) then it will be sent with both a 7:43 and a 19:43, declaring it as both a local phone invoice and a VoIP invoice.&lt;br /&gt;
&lt;br /&gt;
= Batch workflow =&lt;br /&gt;
&lt;br /&gt;
freeside-daily runs FS::Cron::tax_batch::process_tax_batch. If the tax engine has the 'batch' flag (which only AFC has, probably ever), it will call transfer_batch() on the tax engine.&lt;br /&gt;
&lt;br /&gt;
Note that if AFC is active and a customer has pending invoices, bill_and_collect will NOT run collect() for that customer (i.e. it won't run post-billing events, such as charging credit cards or mailing out invoices). We'll come back to that.&lt;br /&gt;
&lt;br /&gt;
transfer_batch() calls create_batch() to write the batch into a file in $cache_dir/Billsoft/upload. These files follow a strict naming scheme: company code + %Y%m%d + two-letter sequential ID starting with 'AA'. So on Jan 12 2017, the first batch would be FIS20170112AA.CSV, the second would be FIS20170112AB.CSV, etc. Normally you should only need one batch per day, but if you somehow send another one, transfer_batch will detect that there's already one named *AA.CSV (in $cache_dir/Billsoft/upload) and name the next one AB, and so on.&lt;br /&gt;
&lt;br /&gt;
create_batch() finds all pending invoices and writes transaction lines into the batch. See the next section for details.&lt;br /&gt;
&lt;br /&gt;
It then compresses the CSV file into &amp;quot;FTP.ZIP&amp;quot; and uploads that, then polls the server waiting for a result batch. Currently this has a timeout of one day (the $TIMEOUT variable); that's probably longer than it should be.&lt;br /&gt;
&lt;br /&gt;
The result batch will be named the same thing as the uploaded CSV file, but .ZIP, and with &amp;quot;R&amp;quot; after the company code (so &amp;quot;FIS'''R'''20170112AA.ZIP&amp;quot;). It will be saved into $cache_dir/Billsoft/download. It contains several files describing many stages of processing, but the one we want is the detail report, ending in _dtl.rpt.csv. This is a CSV file containing one line for each tax on each transaction, which is conveniently the same data structure as our allocation table.&lt;br /&gt;
&lt;br /&gt;
batch_import() reads this file line by line and creates a tax line item for each distinct tax name. It will also create tax_rate_location, tax_class, and tax_rate records as necessary to describe the taxes that are being charged.&lt;br /&gt;
&lt;br /&gt;
After processing the file, batch_import() removes the pending flags from all invoices, then queues a job to run FS::cust_main::Billing::collect on each customer who had an invoice processed this way.&lt;br /&gt;
&lt;br /&gt;
= What we send them =&lt;br /&gt;
&lt;br /&gt;
AFC tax products have two parts, a transaction code (for the kind of service that's being sold, like local telephone, VoIP, etc.) and a service code (for the kind of charge that this is, like monthly subscription, toll, installation, etc.). I internally call these &amp;quot;T-codes&amp;quot; and &amp;quot;S-codes&amp;quot;. See the various AFC manuals (Local Exchange Mapping, VoIP Mapping, etc.) for details.&lt;br /&gt;
&lt;br /&gt;
AFC expects each possibly-taxable feature of the sale to be declared as a separate transaction line. So for each cust_bill_pkg record on the invoice, we send the following transaction lines:&lt;br /&gt;
* Each recurring fee (cust_bill_pkg.recur).&lt;br /&gt;
* Each non-recurring fee (cust_bill_pkg.setup).&lt;br /&gt;
* The number of phone lines (for per-line surcharges).&lt;br /&gt;
* Each CDR (for voip_cdr packages). Yes, this is a large number of records.&lt;br /&gt;
&lt;br /&gt;
In almost all cases, per-line or per-customer) charges such as E911 fees need to be applied once per month, so the package must have a monthly billing cycle.&lt;br /&gt;
&lt;br /&gt;
Fields applying to the whole invoice ('''%bill_properties'''):&lt;br /&gt;
* RequestType: Always 'CalcTaxes' for now.&lt;br /&gt;
* BillTo... (CountryISO, PCode, ZipCode, ZipP4): the customer's billing address.&lt;br /&gt;
* Date: The invoice date (YYYYMMDD).&lt;br /&gt;
* CustomerType: The customer's tax status ('B'usiness, 'R'esidential, etc.).&lt;br /&gt;
* InvoiceNumber: Freeside invnum (echoed in the result file, used to find this invoice again).&lt;br /&gt;
* CustomerNumber: Freeside custnum (echoed in the result file, used for error checking).&lt;br /&gt;
* and some sitewide flags describing the service provider's status:&lt;br /&gt;
** Facilities: Indicates the provider owns the physical infrastructure used to deliver services.&lt;br /&gt;
** Franchise: Indicates that the provider sells service pursuant to a franchise agreement with the jurisdiction.&lt;br /&gt;
** Regulated: Indicates that the provider is a regulated public utility.&lt;br /&gt;
** BusinessClass: Indicates whether the provider is classified as a CLEC or ILEC.&lt;br /&gt;
&lt;br /&gt;
Fields applying to all line items of a specific package ('''%pkg_properties'''):&lt;br /&gt;
* Optional: Freeside billpkgnum (echoed in the result file; the field is just a generic place for a numeric identifier).&lt;br /&gt;
* Sale: Whether this package is sold on a wholesale basis (a part_pkg flag, currently not available in any package).&lt;br /&gt;
&lt;br /&gt;
Fields sent with recurring fee or setup fee lines:&lt;br /&gt;
* Termination... (CountryISO, PCode, ZipCode, ZipP4): the package's service address.&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the recurring or setup fee.&lt;br /&gt;
* Charge: The amount of the sale.&lt;br /&gt;
&lt;br /&gt;
Fields sent with CDR lines:&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the CDR's usage class.&lt;br /&gt;
* Charge: The rated charge for the call.&lt;br /&gt;
* Minutes: The call duration in minutes (including fraction).&lt;br /&gt;
* OriginationNpaNxx: The source number's prefix.&lt;br /&gt;
* TerminationNpaNxx: The destination number's prefix.&lt;br /&gt;
&lt;br /&gt;
Note that if LRN lookup is enabled, the Origination/Termination prefixes will be taken from the resolved LRN numbers.&lt;br /&gt;
&lt;br /&gt;
Fields sent with the &amp;quot;number of phone lines&amp;quot; transaction line:&lt;br /&gt;
* Charges: Zero.&lt;br /&gt;
* Lines: The number of phone services, from FS::part_pkg::calc_units. This transaction line will be sent only if it's greater than zero.&lt;br /&gt;
* TransactionType/ServiceType: The tax product selected with part_pkg.units_taxproductnum. Usually this will be S-code 21 (&amp;quot;Lines&amp;quot;) but can be different for certain local exchange services.&lt;br /&gt;
&lt;br /&gt;
For each T-code that was present on the invoice, we'll also send a &amp;quot;per invoice&amp;quot; transaction line with S-code 43. The &amp;quot;Lines&amp;quot; and &amp;quot;Charges&amp;quot; are both zero on this line.&lt;br /&gt;
&lt;br /&gt;
= Processing the result file =&lt;br /&gt;
&lt;br /&gt;
batch_import() reads records line by line and looks at several columns:&lt;br /&gt;
* InvoiceNumber and Optional columns contain invnum and billpkgnum. We use these to relate the tax back to a taxable line item.&lt;br /&gt;
* TaxTypeID (number) and TaxType (string) are the &amp;quot;kind&amp;quot; of tax, like &amp;quot;Sales Tax&amp;quot; or &amp;quot;Universal Service&amp;quot;. We create a tax_class record with these values if there isn't one already.&lt;br /&gt;
* CountryISO, State, County, Locality, and PCode identify the tax locality. PCodes are unique and we store them as tax_rate_location.geocode. If there isn't a record with this PCode already, we create one.&lt;br /&gt;
* TaxLevelID is the taxing authority: national, state/province, county, city, or unincorporated district. We use this to decide which tax_rate_location field to use in the tax description (for example, &amp;quot;CA Sales Tax&amp;quot; vs. &amp;quot;Los Angeles County Sales Tax&amp;quot;).&lt;br /&gt;
* TaxAmount is the amount of tax, in dollars.&lt;br /&gt;
&lt;br /&gt;
batch_import() constructs the tax name from the geographic name of the taxing authority (like &amp;quot;US&amp;quot; or &amp;quot;NV&amp;quot; or &amp;quot;Las Vegas&amp;quot;) and the TaxType string (like &amp;quot;Sales Tax&amp;quot;) and finds or creates a tax_rate record with that name, and geocode = the PCode of the locality.&lt;br /&gt;
&lt;br /&gt;
It then calls add_tax_item(), an internal method that creates a cust_bill_pkg record with the correct itemdesc (= the tax name). If there's already one (from another line item subject to the same-named tax) then add_tax_item increments the amount of that item.&lt;br /&gt;
&lt;br /&gt;
Finally, it creates a cust_bill_pkg_tax_rate_location allocation record linking the tax and taxed line items, the tax_rate and tax_rate_location records, and the amount.&lt;br /&gt;
&lt;br /&gt;
If there are errors processing the batch, we log them and continue. The invoice will remain in the pending state and be resubmitted in the next batch.&lt;br /&gt;
&lt;br /&gt;
All pending invoices should be cleared at the end of daily processing. There should be a report / notification if any remain in the system after that, but there currently isn't.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9631</id>
		<title>Freeside:4:Documentation:AFC taxes</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9631"/>
				<updated>2017-01-16T21:05:04Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Avalara for Communications (&amp;quot;Billsoft&amp;quot;) =&lt;br /&gt;
&lt;br /&gt;
Avalara currently has two tax services: the one we call &amp;quot;avalara&amp;quot;, which is a general sales-tax services, and the one we call &amp;quot;billsoft&amp;quot; and they call &amp;quot;Avalara for Communications&amp;quot; (AFC).&lt;br /&gt;
&lt;br /&gt;
AFC has a batch-oriented FTP interface that it inherited from Billsoft. This is what we support right now. It also has a nice REST interface which is somewhat experimental. Support for this is planned in the future.&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&lt;br /&gt;
Set the '''tax_data_vendor''' config to &amp;quot;billsoft&amp;quot;, and the '''billsoft-company-code''' config to your three-letter company code.&lt;br /&gt;
&lt;br /&gt;
Create an upload target (Configuration / with hostname ftp.billsoft.com, and the login and password provided by Avalara.&lt;br /&gt;
&lt;br /&gt;
Import the data files. In '''Tools / Importing / Tax rates''', select &amp;quot;Billsoft PCodes&amp;quot; and import the &amp;quot;all_adr.txt&amp;quot; file. Then select &amp;quot;Tax products&amp;quot; and import the &amp;quot;transervdesc.txt&amp;quot; file.&lt;br /&gt;
&lt;br /&gt;
Run freeside-tax-location-update. This will assign PCodes for all existing package locations that don't yet have one, based on their zip codes.&lt;br /&gt;
&lt;br /&gt;
Assign a tax product to each package definition. See below about this.&lt;br /&gt;
&lt;br /&gt;
== Batch workflow ==&lt;br /&gt;
&lt;br /&gt;
freeside-daily runs FS::Cron::tax_batch::process_tax_batch. If the tax engine has the 'batch' flag (which only AFC has, probably ever), it will call transfer_batch() on the tax engine.&lt;br /&gt;
&lt;br /&gt;
Note that if AFC is active and a customer has pending invoices, bill_and_collect will NOT run collect() for that customer (i.e. it won't run post-billing events, such as charging credit cards or mailing out invoices). We'll come back to that.&lt;br /&gt;
&lt;br /&gt;
transfer_batch() calls create_batch() to write the batch into a file in $cache_dir/Billsoft/upload. These files follow a strict naming scheme: company code + %Y%m%d + two-letter sequential ID starting with 'AA'. So on Jan 12 2017, the first batch would be FIS20170112AA.CSV, the second would be FIS20170112AB.CSV, etc. Normally you should only need one batch per day, but if you somehow send another one, transfer_batch will detect that there's already one named *AA.CSV (in $cache_dir/Billsoft/upload) and name the next one AB, and so on.&lt;br /&gt;
&lt;br /&gt;
create_batch() finds all pending invoices and writes transaction lines into the batch. See the next section for details.&lt;br /&gt;
&lt;br /&gt;
It then compresses the CSV file into &amp;quot;FTP.ZIP&amp;quot; and uploads that, then polls the server waiting for a result batch. Currently this has a timeout of one day (the $TIMEOUT variable); that's probably longer than it should be.&lt;br /&gt;
&lt;br /&gt;
The result batch will be named the same thing as the uploaded CSV file, but .ZIP, and with &amp;quot;R&amp;quot; after the company code (so &amp;quot;FIS'''R'''20170112AA.ZIP&amp;quot;). It will be saved into $cache_dir/Billsoft/download. It contains several files describing many stages of processing, but the one we want is the detail report, ending in _dtl.rpt.csv. This is a CSV file containing one line for each tax on each transaction, which is conveniently the same data structure as our allocation table.&lt;br /&gt;
&lt;br /&gt;
batch_import() reads this file line by line and creates a tax line item for each distinct tax name. It will also create tax_rate_location, tax_class, and tax_rate records as necessary to describe the taxes that are being charged.&lt;br /&gt;
&lt;br /&gt;
After processing the file, batch_import() removes the pending flags from all invoices, then queues a job to run FS::cust_main::Billing::collect on each customer who had an invoice processed this way.&lt;br /&gt;
&lt;br /&gt;
== What we send them ==&lt;br /&gt;
&lt;br /&gt;
AFC tax products have two parts, a transaction code (for the kind of service that's being sold, like local telephone, VoIP, etc.) and a service code (for the kind of charge that this is, like monthly subscription, toll, installation, etc.). I internally call these &amp;quot;T-codes&amp;quot; and &amp;quot;S-codes&amp;quot;. See the various AFC manuals (Local Exchange Mapping, VoIP Mapping, etc.) for details.&lt;br /&gt;
&lt;br /&gt;
AFC expects each possibly-taxable feature of the sale to be declared as a separate transaction line. So for each cust_bill_pkg record on the invoice, we send the following transaction lines:&lt;br /&gt;
* Each recurring fee (cust_bill_pkg.recur).&lt;br /&gt;
* Each non-recurring fee (cust_bill_pkg.setup).&lt;br /&gt;
* The number of phone lines (for per-line surcharges).&lt;br /&gt;
* Each CDR (for voip_cdr packages). Yes, this is a large number of records.&lt;br /&gt;
&lt;br /&gt;
In almost all cases, per-line or per-customer) charges such as E911 fees need to be applied once per month, so the package must have a monthly billing cycle.&lt;br /&gt;
&lt;br /&gt;
Fields applying to the whole invoice ('''%bill_properties'''):&lt;br /&gt;
* RequestType: Always 'CalcTaxes' for now.&lt;br /&gt;
* BillTo... (CountryISO, PCode, ZipCode, ZipP4): the customer's billing address.&lt;br /&gt;
* Date: The invoice date (YYYYMMDD).&lt;br /&gt;
* CustomerType: The customer's tax status ('B'usiness, 'R'esidential, etc.).&lt;br /&gt;
* InvoiceNumber: Freeside invnum (echoed in the result file, used to find this invoice again).&lt;br /&gt;
* CustomerNumber: Freeside custnum (echoed in the result file, used for error checking).&lt;br /&gt;
* and some sitewide flags describing the service provider's status:&lt;br /&gt;
** Facilities: Indicates the provider owns the physical infrastructure used to deliver services.&lt;br /&gt;
** Franchise: Indicates that the provider sells service pursuant to a franchise agreement with the jurisdiction.&lt;br /&gt;
** Regulated: Indicates that the provider is a regulated public utility.&lt;br /&gt;
** BusinessClass: Indicates whether the provider is classified as a CLEC or ILEC.&lt;br /&gt;
&lt;br /&gt;
Fields applying to all line items of a specific package ('''%pkg_properties'''):&lt;br /&gt;
* Optional: Freeside billpkgnum (echoed in the result file; the field is just a generic place for a numeric identifier).&lt;br /&gt;
* Sale: Whether this package is sold on a wholesale basis (a part_pkg flag, currently not available in any package).&lt;br /&gt;
&lt;br /&gt;
Fields sent with recurring fee or setup fee lines:&lt;br /&gt;
* Termination... (CountryISO, PCode, ZipCode, ZipP4): the package's service address.&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the recurring or setup fee.&lt;br /&gt;
* Charge: The amount of the sale.&lt;br /&gt;
&lt;br /&gt;
Fields sent with CDR lines:&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the CDR's usage class.&lt;br /&gt;
* Charge: The rated charge for the call.&lt;br /&gt;
* Minutes: The call duration in minutes (including fraction).&lt;br /&gt;
* OriginationNpaNxx: The source number's prefix.&lt;br /&gt;
* TerminationNpaNxx: The destination number's prefix.&lt;br /&gt;
&lt;br /&gt;
Note that if LRN lookup is enabled, the Origination/Termination prefixes will be taken from the resolved LRN numbers.&lt;br /&gt;
&lt;br /&gt;
Fields sent with the &amp;quot;number of phone lines&amp;quot; transaction line:&lt;br /&gt;
* Charges: Zero.&lt;br /&gt;
* Lines: The number of phone services, from FS::part_pkg::calc_units. This transaction line will be sent only if it's greater than zero.&lt;br /&gt;
* TransactionType/ServiceType: The tax product selected with part_pkg.units_taxproductnum. Usually this will be S-code 21 (&amp;quot;Lines&amp;quot;) but can be different for certain local exchange services.&lt;br /&gt;
&lt;br /&gt;
For each T-code that was present on the invoice, we'll also send a &amp;quot;per invoice&amp;quot; transaction line with S-code 43.&lt;br /&gt;
&lt;br /&gt;
== Processing the result file ==&lt;br /&gt;
&lt;br /&gt;
batch_import() reads records line by line and looks at several columns:&lt;br /&gt;
* InvoiceNumber and Optional columns contain invnum and billpkgnum. We use these to relate the tax back to a taxable line item.&lt;br /&gt;
* TaxTypeID (number) and TaxType (string) are the &amp;quot;kind&amp;quot; of tax, like &amp;quot;Sales Tax&amp;quot; or &amp;quot;Universal Service&amp;quot;. We create a tax_class record with these values if there isn't one already.&lt;br /&gt;
* CountryISO, State, County, Locality, and PCode identify the tax locality. PCodes are unique and we store them as tax_rate_location.geocode. If there isn't a record with this PCode already, we create one.&lt;br /&gt;
* TaxLevelID is the taxing authority: national, state/province, county, city, or unincorporated district. We use this to decide which tax_rate_location field to use in the tax description (for example, &amp;quot;CA Sales Tax&amp;quot; vs. &amp;quot;Los Angeles County Sales Tax&amp;quot;).&lt;br /&gt;
* TaxAmount is the amount of tax, in dollars.&lt;br /&gt;
&lt;br /&gt;
batch_import() constructs the tax name from the geographic name of the taxing authority (like &amp;quot;US&amp;quot; or &amp;quot;NV&amp;quot; or &amp;quot;Las Vegas&amp;quot;) and the TaxType string (like &amp;quot;Sales Tax&amp;quot;) and finds or creates a tax_rate record with that name, and geocode = the PCode of the locality.&lt;br /&gt;
&lt;br /&gt;
It then calls add_tax_item(), an internal method that creates a cust_bill_pkg record with the correct itemdesc (= the tax name). If there's already one (from another line item subject to the same-named tax) then add_tax_item increments the amount of that item.&lt;br /&gt;
&lt;br /&gt;
Finally, it creates a cust_bill_pkg_tax_rate_location allocation record linking the tax and taxed line items, the tax_rate and tax_rate_location records, and the amount.&lt;br /&gt;
&lt;br /&gt;
If there are errors processing the batch, we log them and continue. The invoice will remain in the pending state and be resubmitted in the next batch.&lt;br /&gt;
&lt;br /&gt;
All pending invoices should be cleared at the end of daily processing. There should be a report / notification if any remain in the system after that, but there currently isn't.&lt;br /&gt;
&lt;br /&gt;
=== Setting up tax products ===&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;tax product&amp;quot; is a code assigned to a sale to identify what kind of product it is for tax purposes. For example, &amp;quot;recurring charge for local phone service&amp;quot; is a tax product. Tax products are listed in part_pkg_taxproduct.&lt;br /&gt;
&lt;br /&gt;
A package definition (part_pkg) has a single &amp;quot;taxproductnum&amp;quot; field for the base tax product for the package. In the simple case where a package has only one-time or only recurring charges, and no CDR usage charges, this is the only tax product it needs.&lt;br /&gt;
[[File:Editing_afc_taxproduct_1.png]]&lt;br /&gt;
&lt;br /&gt;
More specifically, tax products can be set using part_pkg options named &amp;quot;usage_taxproduct_FOO&amp;quot; where FOO is the word 'setup' or 'recur' or an integer (a usage_class.classnum). The package will then be broken down into setup, recurring non-usage, and usage (separate by usage class), which are taxed separately. See the AFC Telecom Mapping Guidelines and other manuals for details.&lt;br /&gt;
&lt;br /&gt;
For usage-billed phone service, AFC has separate tax product groups (T-codes) for interstate, intrastate, and local services. Local services are typically billed as &amp;quot;Local Exchange charge&amp;quot; (7:5); long-distance charges will be intrastate (2:1), interstate (1:1), or international (1:12) toll charges. Other local exchange charges such as FCC subcriber line / LNP recovery are their own categories. This is all different if you're using VoIP or cellular service.&lt;br /&gt;
[[File:Editing_afc_taxproduct_2.png]]&lt;br /&gt;
&lt;br /&gt;
(Local service also has a separate, parallel set of tax products ending in the word &amp;quot;Bundle&amp;quot;. This is for service plans that don't break out the local and long-distance charge on the bill; for complicated reasons it's exempt from certain taxes. &lt;br /&gt;
&lt;br /&gt;
There should probably be a switch to turn off sending all the CDRs to AFC and just give them a total amount billed in each usage class.&lt;br /&gt;
&lt;br /&gt;
AFC requires that packages that provide &amp;quot;phone lines&amp;quot; of whatever type declare those. Usually this is the &amp;quot;Lines&amp;quot; taxproduct (such as local voice lines, 7:21, or VoIP lines, 19:21) but there are other options such as &amp;quot;PBX trunks&amp;quot; and even &amp;quot;extensions&amp;quot; (which would require more flexibility in counting units than we currently have). The &amp;quot;Per-line tax product&amp;quot; in the package definition sets the part_pkg.units_taxproductnum to this field.&lt;br /&gt;
&lt;br /&gt;
Finally, there are &amp;quot;Invoice&amp;quot; tax products (always S-code 43). These do not need to be configured. If ''any'' tax product on the invoice has a T-code for which there's an Invoice tax product then an additional line will be submitted declaring the Invoice. For example, if an invoice includes a charge for local phone service (7:5) and one for VoIP access charges (19:6) then it will be sent with both a 7:43 and a 19:43, declaring it as both a local phone invoice and a VoIP invoice.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:Editing_afc_taxproduct_2.png&amp;diff=9630</id>
		<title>File:Editing afc taxproduct 2.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:Editing_afc_taxproduct_2.png&amp;diff=9630"/>
				<updated>2017-01-16T21:03:19Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:Editing_afc_taxproduct_1.png&amp;diff=9629</id>
		<title>File:Editing afc taxproduct 1.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:Editing_afc_taxproduct_1.png&amp;diff=9629"/>
				<updated>2017-01-16T21:03:06Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9628</id>
		<title>Freeside:4:Documentation:AFC taxes</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:AFC_taxes&amp;diff=9628"/>
				<updated>2017-01-16T21:01:58Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: add details on AFC&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Avalara for Communications (&amp;quot;Billsoft&amp;quot;) =&lt;br /&gt;
&lt;br /&gt;
Avalara currently has two tax services: the one we call &amp;quot;avalara&amp;quot;, which is a general sales-tax services, and the one we call &amp;quot;billsoft&amp;quot; and they call &amp;quot;Avalara for Communications&amp;quot; (AFC).&lt;br /&gt;
&lt;br /&gt;
AFC has a batch-oriented FTP interface that it inherited from Billsoft. This is what we support right now. It also has a nice REST interface which is somewhat experimental. Support for this is planned in the future.&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&lt;br /&gt;
Set the '''tax_data_vendor''' config to &amp;quot;billsoft&amp;quot;, and the '''billsoft-company-code''' config to your three-letter company code.&lt;br /&gt;
&lt;br /&gt;
Create an upload target (Configuration / with hostname ftp.billsoft.com, and the login and password provided by Avalara.&lt;br /&gt;
&lt;br /&gt;
Import the data files. In '''Tools / Importing / Tax rates''', select &amp;quot;Billsoft PCodes&amp;quot; and import the &amp;quot;all_adr.txt&amp;quot; file. Then select &amp;quot;Tax products&amp;quot; and import the &amp;quot;transervdesc.txt&amp;quot; file.&lt;br /&gt;
&lt;br /&gt;
Run freeside-tax-location-update. This will assign PCodes for all existing package locations that don't yet have one, based on their zip codes.&lt;br /&gt;
&lt;br /&gt;
Assign a tax product to each package definition. See below about this.&lt;br /&gt;
&lt;br /&gt;
== Batch workflow ==&lt;br /&gt;
&lt;br /&gt;
freeside-daily runs FS::Cron::tax_batch::process_tax_batch. If the tax engine has the 'batch' flag (which only AFC has, probably ever), it will call transfer_batch() on the tax engine.&lt;br /&gt;
&lt;br /&gt;
Note that if AFC is active and a customer has pending invoices, bill_and_collect will NOT run collect() for that customer (i.e. it won't run post-billing events, such as charging credit cards or mailing out invoices). We'll come back to that.&lt;br /&gt;
&lt;br /&gt;
transfer_batch() calls create_batch() to write the batch into a file in $cache_dir/Billsoft/upload. These files follow a strict naming scheme: company code + %Y%m%d + two-letter sequential ID starting with 'AA'. So on Jan 12 2017, the first batch would be FIS20170112AA.CSV, the second would be FIS20170112AB.CSV, etc. Normally you should only need one batch per day, but if you somehow send another one, transfer_batch will detect that there's already one named *AA.CSV (in $cache_dir/Billsoft/upload) and name the next one AB, and so on.&lt;br /&gt;
&lt;br /&gt;
create_batch() finds all pending invoices and writes transaction lines into the batch. See the next section for details.&lt;br /&gt;
&lt;br /&gt;
It then compresses the CSV file into &amp;quot;FTP.ZIP&amp;quot; and uploads that, then polls the server waiting for a result batch. Currently this has a timeout of one day (the $TIMEOUT variable); that's probably longer than it should be.&lt;br /&gt;
&lt;br /&gt;
The result batch will be named the same thing as the uploaded CSV file, but .ZIP, and with &amp;quot;R&amp;quot; after the company code (so &amp;quot;FIS'''R'''20170112AA.ZIP&amp;quot;). It will be saved into $cache_dir/Billsoft/download. It contains several files describing many stages of processing, but the one we want is the detail report, ending in _dtl.rpt.csv. This is a CSV file containing one line for each tax on each transaction, which is conveniently the same data structure as our allocation table.&lt;br /&gt;
&lt;br /&gt;
batch_import() reads this file line by line and creates a tax line item for each distinct tax name. It will also create tax_rate_location, tax_class, and tax_rate records as necessary to describe the taxes that are being charged.&lt;br /&gt;
&lt;br /&gt;
After processing the file, batch_import() removes the pending flags from all invoices, then queues a job to run FS::cust_main::Billing::collect on each customer who had an invoice processed this way.&lt;br /&gt;
&lt;br /&gt;
== What we send them ==&lt;br /&gt;
&lt;br /&gt;
AFC tax products have two parts, a transaction code (for the kind of service that's being sold, like local telephone, VoIP, etc.) and a service code (for the kind of charge that this is, like monthly subscription, toll, installation, etc.). I internally call these &amp;quot;T-codes&amp;quot; and &amp;quot;S-codes&amp;quot;. See the various AFC manuals (Local Exchange Mapping, VoIP Mapping, etc.) for details.&lt;br /&gt;
&lt;br /&gt;
AFC expects each possibly-taxable feature of the sale to be declared as a separate transaction line. So for each cust_bill_pkg record on the invoice, we send the following transaction lines:&lt;br /&gt;
* Each recurring fee (cust_bill_pkg.recur).&lt;br /&gt;
* Each non-recurring fee (cust_bill_pkg.setup).&lt;br /&gt;
* The number of phone lines (for per-line surcharges).&lt;br /&gt;
* Each CDR (for voip_cdr packages). Yes, this is a large number of records.&lt;br /&gt;
&lt;br /&gt;
In almost all cases, per-line or per-customer) charges such as E911 fees need to be applied once per month, so the package must have a monthly billing cycle.&lt;br /&gt;
&lt;br /&gt;
Fields applying to the whole invoice ('''%bill_properties'''):&lt;br /&gt;
* RequestType: Always 'CalcTaxes' for now.&lt;br /&gt;
* BillTo... (CountryISO, PCode, ZipCode, ZipP4): the customer's billing address.&lt;br /&gt;
* Date: The invoice date (YYYYMMDD).&lt;br /&gt;
* CustomerType: The customer's tax status ('B'usiness, 'R'esidential, etc.).&lt;br /&gt;
* InvoiceNumber: Freeside invnum (echoed in the result file, used to find this invoice again).&lt;br /&gt;
* CustomerNumber: Freeside custnum (echoed in the result file, used for error checking).&lt;br /&gt;
* and some sitewide flags describing the service provider's status:&lt;br /&gt;
** Facilities: Indicates the provider owns the physical infrastructure used to deliver services.&lt;br /&gt;
** Franchise: Indicates that the provider sells service pursuant to a franchise agreement with the jurisdiction.&lt;br /&gt;
** Regulated: Indicates that the provider is a regulated public utility.&lt;br /&gt;
** BusinessClass: Indicates whether the provider is classified as a CLEC or ILEC.&lt;br /&gt;
&lt;br /&gt;
Fields applying to all line items of a specific package ('''%pkg_properties'''):&lt;br /&gt;
* Optional: Freeside billpkgnum (echoed in the result file; the field is just a generic place for a numeric identifier).&lt;br /&gt;
* Sale: Whether this package is sold on a wholesale basis (a part_pkg flag, currently not available in any package).&lt;br /&gt;
&lt;br /&gt;
Fields sent with recurring fee or setup fee lines:&lt;br /&gt;
* Termination... (CountryISO, PCode, ZipCode, ZipP4): the package's service address.&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the recurring or setup fee.&lt;br /&gt;
* Charge: The amount of the sale.&lt;br /&gt;
&lt;br /&gt;
Fields sent with CDR lines:&lt;br /&gt;
* TransactionType/ServiceType: The tax product for the CDR's usage class.&lt;br /&gt;
* Charge: The rated charge for the call.&lt;br /&gt;
* Minutes: The call duration in minutes (including fraction).&lt;br /&gt;
* OriginationNpaNxx: The source number's prefix.&lt;br /&gt;
* TerminationNpaNxx: The destination number's prefix.&lt;br /&gt;
&lt;br /&gt;
Note that if LRN lookup is enabled, the Origination/Termination prefixes will be taken from the resolved LRN numbers.&lt;br /&gt;
&lt;br /&gt;
Fields sent with the &amp;quot;number of phone lines&amp;quot; transaction line:&lt;br /&gt;
* Charges: Zero.&lt;br /&gt;
* Lines: The number of phone services, from FS::part_pkg::calc_units. This transaction line will be sent only if it's greater than zero.&lt;br /&gt;
* TransactionType/ServiceType: The tax product selected with part_pkg.units_taxproductnum. Usually this will be S-code 21 (&amp;quot;Lines&amp;quot;) but can be different for certain local exchange services.&lt;br /&gt;
&lt;br /&gt;
For each T-code that was present on the invoice, we'll also send a &amp;quot;per invoice&amp;quot; transaction line with S-code 43.&lt;br /&gt;
&lt;br /&gt;
== Processing the result file ==&lt;br /&gt;
&lt;br /&gt;
batch_import() reads records line by line and looks at several columns:&lt;br /&gt;
* InvoiceNumber and Optional columns contain invnum and billpkgnum. We use these to relate the tax back to a taxable line item.&lt;br /&gt;
* TaxTypeID (number) and TaxType (string) are the &amp;quot;kind&amp;quot; of tax, like &amp;quot;Sales Tax&amp;quot; or &amp;quot;Universal Service&amp;quot;. We create a tax_class record with these values if there isn't one already.&lt;br /&gt;
* CountryISO, State, County, Locality, and PCode identify the tax locality. PCodes are unique and we store them as tax_rate_location.geocode. If there isn't a record with this PCode already, we create one.&lt;br /&gt;
* TaxLevelID is the taxing authority: national, state/province, county, city, or unincorporated district. We use this to decide which tax_rate_location field to use in the tax description (for example, &amp;quot;CA Sales Tax&amp;quot; vs. &amp;quot;Los Angeles County Sales Tax&amp;quot;).&lt;br /&gt;
* TaxAmount is the amount of tax, in dollars.&lt;br /&gt;
&lt;br /&gt;
batch_import() constructs the tax name from the geographic name of the taxing authority (like &amp;quot;US&amp;quot; or &amp;quot;NV&amp;quot; or &amp;quot;Las Vegas&amp;quot;) and the TaxType string (like &amp;quot;Sales Tax&amp;quot;) and finds or creates a tax_rate record with that name, and geocode = the PCode of the locality.&lt;br /&gt;
&lt;br /&gt;
It then calls add_tax_item(), an internal method that creates a cust_bill_pkg record with the correct itemdesc (= the tax name). If there's already one (from another line item subject to the same-named tax) then add_tax_item increments the amount of that item.&lt;br /&gt;
&lt;br /&gt;
Finally, it creates a cust_bill_pkg_tax_rate_location allocation record linking the tax and taxed line items, the tax_rate and tax_rate_location records, and the amount.&lt;br /&gt;
&lt;br /&gt;
If there are errors processing the batch, we log them and continue. The invoice will remain in the pending state and be resubmitted in the next batch.&lt;br /&gt;
&lt;br /&gt;
All pending invoices should be cleared at the end of daily processing. There should be a report / notification if any remain in the system after that, but there currently isn't.&lt;br /&gt;
&lt;br /&gt;
=== Setting up tax products ===&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;tax product&amp;quot; is a code assigned to a sale to identify what kind of product it is for tax purposes. For example, &amp;quot;recurring charge for local phone service&amp;quot; is a tax product. Tax products are listed in part_pkg_taxproduct.&lt;br /&gt;
&lt;br /&gt;
A package definition (part_pkg) has a single &amp;quot;taxproductnum&amp;quot; field for the base tax product for the package. In the simple case where a package has only one-time or only recurring charges, and no CDR usage charges, this is the only tax product it needs.&lt;br /&gt;
&lt;br /&gt;
More specifically, tax products can be set using part_pkg options named &amp;quot;usage_taxproduct_FOO&amp;quot; where FOO is the word 'setup' or 'recur' or an integer (a usage_class.classnum). The package will then be broken down into setup, recurring non-usage, and usage (separate by usage class), which are taxed separately. See the AFC Telecom Mapping Guidelines and other manuals for details.&lt;br /&gt;
&lt;br /&gt;
For usage-billed phone service, AFC has separate tax product groups (T-codes) for interstate, intrastate, and local services. Local services are typically billed as &amp;quot;Local Exchange charge&amp;quot; (7:5); long-distance charges will be intrastate (2:1), interstate (1:1), or international (1:12) toll charges. Other local exchange charges such as FCC subcriber line / LNP recovery are their own categories. This is all different if you're using VoIP or cellular service.&lt;br /&gt;
&lt;br /&gt;
(Local service also has a separate, parallel set of tax products ending in the word &amp;quot;Bundle&amp;quot;. This is for service plans that don't break out the local and long-distance charge on the bill; for complicated reasons it's exempt from certain taxes. &lt;br /&gt;
&lt;br /&gt;
There should probably be a switch to turn off sending all the CDRs to AFC and just give them a total amount billed in each usage class.&lt;br /&gt;
&lt;br /&gt;
AFC requires that packages that provide &amp;quot;phone lines&amp;quot; of whatever type declare those. Usually this is the &amp;quot;Lines&amp;quot; taxproduct (such as local voice lines, 7:21, or VoIP lines, 19:21) but there are other options such as &amp;quot;PBX trunks&amp;quot; and even &amp;quot;extensions&amp;quot; (which would require more flexibility in counting units than we currently have). The &amp;quot;Per-line tax product&amp;quot; in the package definition sets the part_pkg.units_taxproductnum to this field.&lt;br /&gt;
&lt;br /&gt;
Finally, there are &amp;quot;Invoice&amp;quot; tax products (always S-code 43). These do not need to be configured. If ''any'' tax product on the invoice has a T-code for which there's an Invoice tax product then an additional line will be submitted declaring the Invoice. For example, if an invoice includes a charge for local phone service (7:5) and one for VoIP access charges (19:6) then it will be sent with both a 7:43 and a 19:43, declaring it as both a local phone invoice and a VoIP invoice.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:TaxEngine&amp;diff=9627</id>
		<title>Freeside:4:Documentation:TaxEngine</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:TaxEngine&amp;diff=9627"/>
				<updated>2017-01-16T21:01:51Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: add details on AFC&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tax Engines =&lt;br /&gt;
&lt;br /&gt;
Freeside 4 refactors the code for tax calculation into &amp;quot;tax engines&amp;quot;. This document describes the interface to use a tax engine for tax calculation, or to implement a new tax calculation method.&lt;br /&gt;
&lt;br /&gt;
A tax engine is a method for calculating tax on invoices. Before version 4, Freeside supported two methods:&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;Internal&amp;quot; tax, using the ''cust_main_county'' table to define tax rates manually. Tax elements on each line item are stored in ''cust_bill_pkg_tax_location'' and linked to both ''cust_main_county'' and ''cust_location''.&lt;br /&gt;
* &amp;quot;External&amp;quot; tax using tax tables downloaded from CCH. The name is misleading, as Freeside performs the tax calculations internally using tables stored in the Freeside database.&lt;br /&gt;
&lt;br /&gt;
Starting with version 4, &amp;quot;External&amp;quot; is renamed to &amp;quot;CCH&amp;quot;, and the following new methods are available:&lt;br /&gt;
* [http://www.avalara.com Avalara]&lt;br /&gt;
* [[Freeside:4:Documentation:AFC_taxes|Avalara for Communications, AKA Billsoft]]&lt;br /&gt;
* [http://www.suretax.com SureTax]&lt;br /&gt;
&lt;br /&gt;
== External interface ==&lt;br /&gt;
&lt;br /&gt;
The ''enable_taxproducts'' config sets the tax engine to use on the system. FS::TaxEngine-&amp;gt;new will return an object of that type.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;normal&amp;quot; workflow is:&lt;br /&gt;
&lt;br /&gt;
* As each line item is created, call ''add_sale'', passing that line item. Not all tax engines use this.&lt;br /&gt;
* Insert the invoice and its line items.&lt;br /&gt;
* Call ''calculate_taxes'', passing the FS::cust_bill object.&lt;br /&gt;
** This calls ''make_taxlines'' to create a list of tax elements.&lt;br /&gt;
** It then calls ''consolidate_taxlines'' to group the elements by tax name and create a list of tax line items (FS::cust_bill_pkg objects).&lt;br /&gt;
** ''calculate_taxes'' will return that list, with each line item linked to an array of tax elements.&lt;br /&gt;
* Insert the tax line items.&lt;br /&gt;
* Add the total tax to the invoice's &amp;quot;charged&amp;quot; field, and set the &amp;quot;pending&amp;quot; field to null.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
FS::TaxEngine objects also implement a few peripheral methods:&lt;br /&gt;
&lt;br /&gt;
* ''info'': Returns some metadata about the tax engine, such as which tables it uses for tax rates and elements, whether it requires batch submission of invoices, allows manual location selection, etc.&lt;br /&gt;
* ''cust_tax_locations'': Takes an FS::cust_location object, returns a list of tax jurisdictions that it could possibly be in. Only used if manual location selection is allowed.&lt;br /&gt;
* ''add_taxproduct'': A &amp;quot;taxproduct&amp;quot; (FS::part_pkg_taxproduct) is a category of taxable products. Some tax systems predefine these and provide a large list. Others expect the user to define them; in that case, this method is called when the user tries to manually add a taxproduct to the system.&lt;br /&gt;
&lt;br /&gt;
Finally, each tax engine can provide a UI component (under browse/part_pkg_taxproduct/) to select the taxproduct when editing a package definition. This is necessary because the part_pkg_taxproduct table is used in different ways by different engines. See examples in that directory.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
'''You must implement either ''make_taxlines'' or ''calculate_taxes''.''' Everything else is optional.&lt;br /&gt;
&lt;br /&gt;
If your calculation method produces pre-consolidated line items (that is, one line for each distinct tax name), it should be implemented as ''calculate_taxes''. This takes an FS::cust_bill object and returns a list of FS::cust_bill_pkg objects for the line items. Each of these should have tax elements (either FS::cust_bill_pkg_tax_location or FS::cust_bill_pkg_tax_rate_location) that reference the &amp;quot;billpkgnum&amp;quot; fields of the line items tax was charged on.&lt;br /&gt;
&lt;br /&gt;
If your calculation method just returns a list of taxes for each taxed line item, it should be implemented as ''make_taxlines''. This also takes an FS::cust_bill object, and returns a list of tax elements. The tax line items will be created from those. In this case, the ''info'' hash will need to include ''link_table'', the name of the table where you store tax elements (e.g. cust_bill_pkg_tax_location).&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Tax_engine_documentation&amp;diff=9626</id>
		<title>Tax engine documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Tax_engine_documentation&amp;diff=9626"/>
				<updated>2017-01-13T00:27:20Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tax calculation in general =&lt;br /&gt;
&lt;br /&gt;
Freeside supports a few mechanisms for calculating taxes. On version 4 and later, these share an internal interface, FS::TaxEngine.&lt;br /&gt;
&lt;br /&gt;
On 3.x (and as far back as 1.7, I think) there are two tax mechanisms, without any kind of common interface. All references to them are hardcoded. The systems are:&lt;br /&gt;
&lt;br /&gt;
* [[Internal_taxes|&amp;quot;Internal&amp;quot; taxes via ''cust_main_county''.]] These require manual setup, and the kinds of taxes that it handles are very limited.&lt;br /&gt;
* &amp;quot;CCH&amp;quot; taxes via ''tax_rate''.&lt;br /&gt;
&lt;br /&gt;
On 4.x, we add:&lt;br /&gt;
* SureTax (the online version of CCH)&lt;br /&gt;
* Avalara's &amp;quot;AvaTax&amp;quot; product&lt;br /&gt;
* [[AFC_taxes|Avalara's &amp;quot;AvaTax For Communications&amp;quot; product]], formerly known as &amp;quot;Billsoft&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Requirements =&lt;br /&gt;
&lt;br /&gt;
For many clients (those not selling regulated voice telecom services, and those with national sales tax such as Australia and Canada), taxes are fairly simple. Each customer is located in some tax jurisdiction, which defines a tax rate. This is multiplied by the sale amount to that customer to obtain the tax amount, which is added to their invoice.&lt;br /&gt;
&lt;br /&gt;
For voice call service, it's a lot more complex. A typical U.S. phone service might require the carrier to collect:&lt;br /&gt;
&lt;br /&gt;
* State, county, and local sales tax, typically on the entire sale amount.&lt;br /&gt;
* State universal service fund (USF) and TDD fund taxes, based on either the sale amount or just the toll charges.&lt;br /&gt;
* Federal USF and excise tax, as a percentage of interstate long-distance charges.&lt;br /&gt;
* E911 fees.&lt;br /&gt;
&lt;br /&gt;
Other complications related to taxes:&lt;br /&gt;
&lt;br /&gt;
* Some customers are exempt from some or all taxes.&lt;br /&gt;
* Some packages are exempt from some or all taxes, also.&lt;br /&gt;
* Some customers are exempt from some taxes up to some threshold amount. (Called &amp;quot;Texas tax&amp;quot; in the code.)&lt;br /&gt;
* In general, crediting a sale that was taxed should &amp;quot;give tax back&amp;quot; (that is, also credit the tax that applied to that sale). Because the credit can be for any amount, we have to track which line items the credit was applied to, and how much was applied to tax or non-tax amounts.&lt;br /&gt;
* And crediting a sale that was exempt up to a threshold should &amp;quot;give back&amp;quot; exemption, also.&lt;br /&gt;
* We should be able to report on the taxable sales, tax-exempt sales, tax credited, and tax collected in any billing period.&lt;br /&gt;
&lt;br /&gt;
= High-level overview =&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Tax line items&amp;quot; are cust_bill_pkg records that have pkgnum = 0 and feepart = null. They will also only have setup fees, not recurring fees, and may have &amp;quot;itemdesc&amp;quot; set to the name of the tax as it should appear on the invoice.&lt;br /&gt;
&lt;br /&gt;
Starting with Freeside 3.x, every tax line item has &amp;quot;allocation records&amp;quot; linking it to both the sale items that it was charged on, and its tax definition. These have two foreign keys to cust_bill_pkg: &amp;quot;billpkgnum&amp;quot; (the tax charge) and &amp;quot;taxable_billpkgnum&amp;quot; (the taxed sale).&lt;br /&gt;
&lt;br /&gt;
Tax calculation happens as the last step in creating an invoice (FS::cust_main::Billing::bill). It generates a list of tax line items (FS::cust_bill_pkg objects) each linked to a list of tax allocation records.&lt;br /&gt;
&lt;br /&gt;
In Freeside 3.x, this involved calling two methods in FS::cust_main::Billing:&lt;br /&gt;
* _handle_taxes: Identify the tax definitions that apply to this invoice, and make a list of all sale line items that are subject to each tax definition. This also finds any exemptions that apply.&lt;br /&gt;
* calculate_taxes: For each tax definition, calculate its tax on the set of taxable items it applies to. Create an allocation record for each of them. Then consolidate those records to make a single tax line item for each ''tax name''. Insert those records and the allocation records.&lt;br /&gt;
&lt;br /&gt;
All of this was done before ''any'' of the records were inserted, which involved building a large free-floating data structure with references from tax line items to allocation records to taxable items to exemption records. FS::cust_bill_pkg::insert would then traverse this structure and insert the records, turning in-memory references into foreign keys.&lt;br /&gt;
&lt;br /&gt;
In 4.x this is somewhat simpler: FS::cust_main::Billing inserts the invoice, along with all its package line items, then calls FS::TaxEngine::calculate_taxes to get a list of tax line items. It then inserts all of them and adds the tax amount to cust_bill.charged. This was needed to support batch mode, since the invoice may sit for the rest of the day before being submitted for tax calculation. Before the taxes have been added, the invoice has the 'pending' flag set so that we know it's not finished.&lt;br /&gt;
&lt;br /&gt;
'''tax_data_vendor''' selects the tax service to use. If it's a batch-mode service (i.e. billsoft), freeside-daily calls FS::TaxEngine::transfer_batch, which takes all pending invoices and sends them in for batch calculation, then appends the resulting taxes to the invoices and marks them as non-pending.&lt;br /&gt;
&lt;br /&gt;
= Schema =&lt;br /&gt;
&lt;br /&gt;
== For internal taxes ==&lt;br /&gt;
&lt;br /&gt;
[[File:Internal_tax_schema.png]]&lt;br /&gt;
&lt;br /&gt;
Tax rates are defined in cust_main_county, which has the following information:&lt;br /&gt;
* The tax rate (&amp;quot;tax&amp;quot;), as a percentage.&lt;br /&gt;
* The tax name.&lt;br /&gt;
* Where the tax applies: country, state, county, city, and district. Except country, they can be null.&lt;br /&gt;
* What the tax applies to: a &amp;quot;taxclass&amp;quot; string, and &amp;quot;setuptax&amp;quot; and &amp;quot;recurtax&amp;quot; flags indicating that setup / recur charges are exempt. (Yes, the names are confusing.)&lt;br /&gt;
* &amp;quot;exempt_amount&amp;quot;: the per-customer monthly exemption if any.&lt;br /&gt;
* &amp;quot;source&amp;quot;: keeps track of which tax rates are automatically maintained. Currently only applies to the Washington State Sales Tax.&lt;br /&gt;
&lt;br /&gt;
Tax allocation records are in cust_bill_pkg_tax_location, and contain:&lt;br /&gt;
* Foreign keys linking to the cust_bill_pkg records of both the tax and the taxed sale.&lt;br /&gt;
* A foreign key to the cust_main_county record defining the tax.&lt;br /&gt;
* The amount of tax.&lt;br /&gt;
* &amp;quot;taxtype&amp;quot;, always 'FS::cust_main_county' for now.&lt;br /&gt;
* &amp;quot;pkgnum&amp;quot; and &amp;quot;locationnum&amp;quot; fields, which are now redundant, since the taxed sale is already linked back to the package.&lt;br /&gt;
&lt;br /&gt;
Exempt sale records are in cust_tax_exempt_pkg. One exempt sale record is created for each tax that the sale was exempted from. They contain:&lt;br /&gt;
* The billpkgnum of the sale.&lt;br /&gt;
* The cust_main_county record (taxnum) it was exempted from.&lt;br /&gt;
* The year and month that the exemption occurred. This is to make monthly exemptions easier to calculate.&lt;br /&gt;
* The sale amount that was exempted.&lt;br /&gt;
* Several &amp;quot;exempt_&amp;quot; flags indicating why the sale was exempt: because the customer is tax-exempt (&amp;quot;cust&amp;quot;) or selectively exempt (&amp;quot;cust_taxname&amp;quot;), or the tax doesn't apply to setup / recur fees, or there's a monthly exemption.&lt;br /&gt;
* A foreign key to cust_credit_bill_pkg. This is used for negative exemption records created when a credit is applied to a tax-exempt sale. For &amp;quot;normal&amp;quot; exempt sale records it will be null.&lt;br /&gt;
&lt;br /&gt;
Package tax classes are in the part_pkg.taxclass field. This is a string, not a foreign key, though there's a list of allowed tax classes in part_pkg_taxclass.&lt;br /&gt;
&lt;br /&gt;
== For external/vendor taxes ==&lt;br /&gt;
&lt;br /&gt;
[[File:External_tax_schema.png]]&lt;br /&gt;
&lt;br /&gt;
Most tables related to external taxes have a &amp;quot;data_vendor&amp;quot; field which identifies which tax service vendor the record came from / applies to.&lt;br /&gt;
&lt;br /&gt;
Tax rates are defined in tax_rate. For CCH taxes, all fields in tax_rate are used for the tax calculation. For Suretax / Avalara taxes, we still create a record for each tax name + jurisdiction as the tax service tells us about them, but the other fields are left empty. The essential fields are:&lt;br /&gt;
* The tax name.&lt;br /&gt;
* data_vendor&lt;br /&gt;
* Where the tax applies, in terms of the &amp;quot;geocode&amp;quot; field, which links to tax_rate_location.&lt;br /&gt;
* What kind of tax it is: &amp;quot;taxclassnum&amp;quot;, a foreign key to tax_class.&lt;br /&gt;
&lt;br /&gt;
The allocation records are in cust_bill_pkg_tax_rate_location and are pretty similar to the ones for internal taxes, except that they also have &amp;quot;taxratelocationnum&amp;quot;, a link to tax_rate_location.&lt;br /&gt;
&lt;br /&gt;
External taxes have a more complex system for classifying the taxability of packages. &amp;quot;Tax products&amp;quot; are defined by the vendor, and stored locally in part_pkg_taxproduct:&lt;br /&gt;
* &amp;quot;taxproduct&amp;quot; is the vendor's code for this product.&lt;br /&gt;
* &amp;quot;description&amp;quot; is how it should look to the Freeside user.&lt;br /&gt;
&lt;br /&gt;
Each part_pkg can then be associated with a tax product via the taxproductnum field. However, a single package can generate several kinds of charges with different tax treatment. So there are also part_pkg options named &amp;quot;usage_taxproductnum_FOO&amp;quot; where FOO = &amp;quot;setup&amp;quot;, &amp;quot;recur&amp;quot;, or a usage class number. This allows different taxes to apply to recurring and non-recurring fees, and to local, interstate, or international calls.&lt;br /&gt;
&lt;br /&gt;
Tax vendors also typically have their own coding scheme for customer locations. We refer to these codes as &amp;quot;geocode&amp;quot;. cust_location.geocode is the geocode of a package location. The geocodes are defined in tax_rate_location, which has these fields:&lt;br /&gt;
* data_vendor&lt;br /&gt;
* geocode&lt;br /&gt;
* country, state, county, city&lt;br /&gt;
&lt;br /&gt;
part_pkg_taxrate relates tax products to the taxes that apply to them in each geocoded region. This is the core of the CCH tax engine and is unused for any other tax system.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:Internal_tax_schema.png&amp;diff=9625</id>
		<title>File:Internal tax schema.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:Internal_tax_schema.png&amp;diff=9625"/>
				<updated>2017-01-13T00:21:16Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Mark uploaded a new version of &amp;amp;quot;File:Internal tax schema.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:Internal_tax_schema.png&amp;diff=9624</id>
		<title>File:Internal tax schema.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:Internal_tax_schema.png&amp;diff=9624"/>
				<updated>2017-01-12T23:51:13Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Tax_engine_documentation&amp;diff=9623</id>
		<title>Tax engine documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Tax_engine_documentation&amp;diff=9623"/>
				<updated>2017-01-12T23:39:44Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: add schema graphic&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tax calculation in general =&lt;br /&gt;
&lt;br /&gt;
Freeside supports a few mechanisms for calculating taxes. On version 4 and later, these share an internal interface, FS::TaxEngine.&lt;br /&gt;
&lt;br /&gt;
On 3.x (and as far back as 1.7, I think) there are two tax mechanisms, without any kind of common interface. All references to them are hardcoded. The systems are:&lt;br /&gt;
&lt;br /&gt;
* [[Internal_taxes|&amp;quot;Internal&amp;quot; taxes via ''cust_main_county''.]] These require manual setup, and the kinds of taxes that it handles are very limited.&lt;br /&gt;
* &amp;quot;CCH&amp;quot; taxes via ''tax_rate''.&lt;br /&gt;
&lt;br /&gt;
On 4.x, we add:&lt;br /&gt;
* SureTax (the online version of CCH)&lt;br /&gt;
* Avalara's &amp;quot;AvaTax&amp;quot; product&lt;br /&gt;
* [[AFC_taxes|Avalara's &amp;quot;AvaTax For Communications&amp;quot; product]], formerly known as &amp;quot;Billsoft&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= Requirements =&lt;br /&gt;
&lt;br /&gt;
For many clients (those not selling regulated voice telecom services, and those with national sales tax such as Australia and Canada), taxes are fairly simple. Each customer is located in some tax jurisdiction, which defines a tax rate. This is multiplied by the sale amount to that customer to obtain the tax amount, which is added to their invoice.&lt;br /&gt;
&lt;br /&gt;
For voice call service, it's a lot more complex. A typical U.S. phone service might require the carrier to collect:&lt;br /&gt;
&lt;br /&gt;
* State, county, and local sales tax, typically on the entire sale amount.&lt;br /&gt;
* State universal service fund (USF) and TDD fund taxes, based on either the sale amount or just the toll charges.&lt;br /&gt;
* Federal USF and excise tax, as a percentage of interstate long-distance charges.&lt;br /&gt;
* E911 fees.&lt;br /&gt;
&lt;br /&gt;
Other complications related to taxes:&lt;br /&gt;
&lt;br /&gt;
* Some customers are exempt from some or all taxes.&lt;br /&gt;
* Some packages are exempt from some or all taxes, also.&lt;br /&gt;
* Some customers are exempt from some taxes up to some threshold amount. (Called &amp;quot;Texas tax&amp;quot; in the code.)&lt;br /&gt;
* In general, crediting a sale that was taxed should &amp;quot;give tax back&amp;quot; (that is, also credit the tax that applied to that sale). Because the credit can be for any amount, we have to track which line items the credit was applied to, and how much was applied to tax or non-tax amounts.&lt;br /&gt;
* And crediting a sale that was exempt up to a threshold should &amp;quot;give back&amp;quot; exemption, also.&lt;br /&gt;
* We should be able to report on the taxable sales, tax-exempt sales, tax credited, and tax collected in any billing period.&lt;br /&gt;
&lt;br /&gt;
= High-level overview =&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Tax line items&amp;quot; are cust_bill_pkg records that have pkgnum = 0 and feepart = null. They will also only have setup fees, not recurring fees, and may have &amp;quot;itemdesc&amp;quot; set to the name of the tax as it should appear on the invoice.&lt;br /&gt;
&lt;br /&gt;
Starting with Freeside 3.x, every tax line item has &amp;quot;allocation records&amp;quot; linking it to both the sale items that it was charged on, and its tax definition. These have two foreign keys to cust_bill_pkg: &amp;quot;billpkgnum&amp;quot; (the tax charge) and &amp;quot;taxable_billpkgnum&amp;quot; (the taxed sale).&lt;br /&gt;
&lt;br /&gt;
Tax calculation happens as the last step in creating an invoice (FS::cust_main::Billing::bill). It generates a list of tax line items (FS::cust_bill_pkg objects) each linked to a list of tax allocation records.&lt;br /&gt;
&lt;br /&gt;
In Freeside 3.x, this involved calling two methods in FS::cust_main::Billing:&lt;br /&gt;
* _handle_taxes: Identify the tax definitions that apply to this invoice, and make a list of all sale line items that are subject to each tax definition. This also finds any exemptions that apply.&lt;br /&gt;
* calculate_taxes: For each tax definition, calculate its tax on the set of taxable items it applies to. Create an allocation record for each of them. Then consolidate those records to make a single tax line item for each ''tax name''. Insert those records and the allocation records.&lt;br /&gt;
&lt;br /&gt;
All of this was done before ''any'' of the records were inserted, which involved building a large free-floating data structure with references from tax line items to allocation records to taxable items to exemption records. FS::cust_bill_pkg::insert would then traverse this structure and insert the records, turning in-memory references into foreign keys.&lt;br /&gt;
&lt;br /&gt;
In 4.x this is somewhat simpler: FS::cust_main::Billing inserts the invoice, along with all its package line items, then calls FS::TaxEngine::calculate_taxes to get a list of tax line items. It then inserts all of them and adds the tax amount to cust_bill.charged. This was needed to support batch mode, since the invoice may sit for the rest of the day before being submitted for tax calculation. Before the taxes have been added, the invoice has the 'pending' flag set so that we know it's not finished.&lt;br /&gt;
&lt;br /&gt;
'''tax_data_vendor''' selects the tax service to use. If it's a batch-mode service (i.e. billsoft), freeside-daily calls FS::TaxEngine::transfer_batch, which takes all pending invoices and sends them in for batch calculation, then appends the resulting taxes to the invoices and marks them as non-pending.&lt;br /&gt;
&lt;br /&gt;
= Schema =&lt;br /&gt;
&lt;br /&gt;
== For internal taxes ==&lt;br /&gt;
&lt;br /&gt;
Tax rates are defined in cust_main_county, which has the following information:&lt;br /&gt;
* The tax rate (&amp;quot;tax&amp;quot;), as a percentage.&lt;br /&gt;
* The tax name.&lt;br /&gt;
* Where the tax applies: country, state, county, city, and district. Except country, they can be null.&lt;br /&gt;
* What the tax applies to: a &amp;quot;taxclass&amp;quot; string, and &amp;quot;setuptax&amp;quot; and &amp;quot;recurtax&amp;quot; flags indicating that setup / recur charges are exempt. (Yes, the names are confusing.)&lt;br /&gt;
* &amp;quot;exempt_amount&amp;quot;: the per-customer monthly exemption if any.&lt;br /&gt;
* &amp;quot;source&amp;quot;: keeps track of which tax rates are automatically maintained. Currently only applies to the Washington State Sales Tax.&lt;br /&gt;
&lt;br /&gt;
Tax allocation records are in cust_bill_pkg_tax_location, and contain:&lt;br /&gt;
* Foreign keys linking to the cust_bill_pkg records of both the tax and the taxed sale.&lt;br /&gt;
* A foreign key to the cust_main_county record defining the tax.&lt;br /&gt;
* The amount of tax.&lt;br /&gt;
* &amp;quot;taxtype&amp;quot;, always 'FS::cust_main_county' for now.&lt;br /&gt;
* &amp;quot;pkgnum&amp;quot; and &amp;quot;locationnum&amp;quot; fields, which are now redundant, since the taxed sale is already linked back to the package.&lt;br /&gt;
&lt;br /&gt;
Exempt sale records are in cust_tax_exempt_pkg. One exempt sale record is created for each tax that the sale was exempted from. They contain:&lt;br /&gt;
* The billpkgnum of the sale.&lt;br /&gt;
* The cust_main_county record (taxnum) it was exempted from.&lt;br /&gt;
* The year and month that the exemption occurred. This is to make monthly exemptions easier to calculate.&lt;br /&gt;
* The sale amount that was exempted.&lt;br /&gt;
* Several &amp;quot;exempt_&amp;quot; flags indicating why the sale was exempt: because the customer is tax-exempt (&amp;quot;cust&amp;quot;) or selectively exempt (&amp;quot;cust_taxname&amp;quot;), or the tax doesn't apply to setup / recur fees, or there's a monthly exemption.&lt;br /&gt;
* A foreign key to cust_credit_bill_pkg. This is used for negative exemption records created when a credit is applied to a tax-exempt sale. For &amp;quot;normal&amp;quot; exempt sale records it will be null.&lt;br /&gt;
&lt;br /&gt;
Package tax classes are in the part_pkg.taxclass field. This is a string, not a foreign key, though there's a list of allowed tax classes in part_pkg_taxclass.&lt;br /&gt;
&lt;br /&gt;
== For external/vendor taxes ==&lt;br /&gt;
&lt;br /&gt;
[[File:External_tax_schema.png]]&lt;br /&gt;
&lt;br /&gt;
Most tables related to external taxes have a &amp;quot;data_vendor&amp;quot; field which identifies which tax service vendor the record came from / applies to.&lt;br /&gt;
&lt;br /&gt;
Tax rates are defined in tax_rate. For CCH taxes, all fields in tax_rate are used for the tax calculation. For Suretax / Avalara taxes, we still create a record for each tax name + jurisdiction as the tax service tells us about them, but the other fields are left empty. The essential fields are:&lt;br /&gt;
* The tax name.&lt;br /&gt;
* data_vendor&lt;br /&gt;
* Where the tax applies, in terms of the &amp;quot;geocode&amp;quot; field, which links to tax_rate_location.&lt;br /&gt;
* What kind of tax it is: &amp;quot;taxclassnum&amp;quot;, a foreign key to tax_class.&lt;br /&gt;
&lt;br /&gt;
The allocation records are in cust_bill_pkg_tax_rate_location and are pretty similar to the ones for internal taxes, except that they also have &amp;quot;taxratelocationnum&amp;quot;, a link to tax_rate_location.&lt;br /&gt;
&lt;br /&gt;
External taxes have a more complex system for classifying the taxability of packages. &amp;quot;Tax products&amp;quot; are defined by the vendor, and stored locally in part_pkg_taxproduct:&lt;br /&gt;
* &amp;quot;taxproduct&amp;quot; is the vendor's code for this product.&lt;br /&gt;
* &amp;quot;description&amp;quot; is how it should look to the Freeside user.&lt;br /&gt;
&lt;br /&gt;
Each part_pkg can then be associated with a tax product via the taxproductnum field. However, a single package can generate several kinds of charges with different tax treatment. So there are also part_pkg options named &amp;quot;usage_taxproductnum_FOO&amp;quot; where FOO = &amp;quot;setup&amp;quot;, &amp;quot;recur&amp;quot;, or a usage class number. This allows different taxes to apply to recurring and non-recurring fees, and to local, interstate, or international calls.&lt;br /&gt;
&lt;br /&gt;
Tax vendors also typically have their own coding scheme for customer locations. We refer to these codes as &amp;quot;geocode&amp;quot;. cust_location.geocode is the geocode of a package location. The geocodes are defined in tax_rate_location, which has these fields:&lt;br /&gt;
* data_vendor&lt;br /&gt;
* geocode&lt;br /&gt;
* country, state, county, city&lt;br /&gt;
&lt;br /&gt;
part_pkg_taxrate relates tax products to the taxes that apply to them in each geocoded region. This is the core of the CCH tax engine and is unused for any other tax system.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:External_tax_schema.png&amp;diff=9622</id>
		<title>File:External tax schema.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:External_tax_schema.png&amp;diff=9622"/>
				<updated>2017-01-12T23:38:29Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Mark uploaded a new version of &amp;amp;quot;File:External tax schema.png&amp;amp;quot;: resize&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Schema diagram for the tax engine, for external taxes (except CCH)&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:External_tax_schema.png&amp;diff=9621</id>
		<title>File:External tax schema.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:External_tax_schema.png&amp;diff=9621"/>
				<updated>2017-01-12T23:37:11Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Schema diagram for the tax engine, for external taxes (except CCH)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Schema diagram for the tax engine, for external taxes (except CCH)&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Tax_engine_documentation&amp;diff=9620</id>
		<title>Tax engine documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Tax_engine_documentation&amp;diff=9620"/>
				<updated>2017-01-12T23:34:09Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;= Tax calculation in general =  Freeside supports a few mechanisms for calculating taxes. On version 4 and later, these share an internal interface, FS::TaxEngine.  On 3.x (an...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tax calculation in general =&lt;br /&gt;
&lt;br /&gt;
Freeside supports a few mechanisms for calculating taxes. On version 4 and later, these share an internal interface, FS::TaxEngine.&lt;br /&gt;
&lt;br /&gt;
On 3.x (and as far back as 1.7, I think) there are two tax mechanisms, without any kind of common interface. All references to them are hardcoded. The systems are:&lt;br /&gt;
&lt;br /&gt;
* [[Internal_taxes|&amp;quot;Internal&amp;quot; taxes via ''cust_main_county''.]] These require manual setup, and the kinds of taxes that it handles are very limited.&lt;br /&gt;
* &amp;quot;CCH&amp;quot; taxes via ''tax_rate''.&lt;br /&gt;
&lt;br /&gt;
On 4.x, we add:&lt;br /&gt;
* SureTax (the online version of CCH)&lt;br /&gt;
* Avalara's &amp;quot;AvaTax&amp;quot; product&lt;br /&gt;
* [[AFC_taxes|Avalara's &amp;quot;AvaTax For Communications&amp;quot; product]], formerly known as &amp;quot;Billsoft&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
For many clients (those not selling regulated voice telecom services, and those with national sales tax such as Australia and Canada), taxes are fairly simple. Each customer is located in some tax jurisdiction, which defines a tax rate. This is multiplied by the sale amount to that customer to obtain the tax amount, which is added to their invoice.&lt;br /&gt;
&lt;br /&gt;
For voice call service, it's a lot more complex. A typical U.S. phone service might require the carrier to collect:&lt;br /&gt;
&lt;br /&gt;
* State, county, and local sales tax, typically on the entire sale amount.&lt;br /&gt;
* State universal service fund (USF) and TDD fund taxes, based on either the sale amount or just the toll charges.&lt;br /&gt;
* Federal USF and excise tax, as a percentage of interstate long-distance charges.&lt;br /&gt;
* E911 fees.&lt;br /&gt;
&lt;br /&gt;
Other complications related to taxes:&lt;br /&gt;
&lt;br /&gt;
* Some customers are exempt from some or all taxes.&lt;br /&gt;
* Some packages are exempt from some or all taxes, also.&lt;br /&gt;
* Some customers are exempt from some taxes up to some threshold amount. (Called &amp;quot;Texas tax&amp;quot; in the code.)&lt;br /&gt;
* In general, crediting a sale that was taxed should &amp;quot;give tax back&amp;quot; (that is, also credit the tax that applied to that sale). Because the credit can be for any amount, we have to track which line items the credit was applied to, and how much was applied to tax or non-tax amounts.&lt;br /&gt;
* And crediting a sale that was exempt up to a threshold should &amp;quot;give back&amp;quot; exemption, also.&lt;br /&gt;
* We should be able to report on the taxable sales, tax-exempt sales, tax credited, and tax collected in any billing period.&lt;br /&gt;
&lt;br /&gt;
== High-level overview ==&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Tax line items&amp;quot; are cust_bill_pkg records that have pkgnum = 0 and feepart = null. They will also only have setup fees, not recurring fees, and may have &amp;quot;itemdesc&amp;quot; set to the name of the tax as it should appear on the invoice.&lt;br /&gt;
&lt;br /&gt;
Starting with Freeside 3.x, every tax line item has &amp;quot;allocation records&amp;quot; linking it to both the sale items that it was charged on, and its tax definition. These have two foreign keys to cust_bill_pkg: &amp;quot;billpkgnum&amp;quot; (the tax charge) and &amp;quot;taxable_billpkgnum&amp;quot; (the taxed sale).&lt;br /&gt;
&lt;br /&gt;
Tax calculation happens as the last step in creating an invoice (FS::cust_main::Billing::bill). It generates a list of tax line items (FS::cust_bill_pkg objects) each linked to a list of tax allocation records.&lt;br /&gt;
&lt;br /&gt;
In Freeside 3.x, this involved calling two methods in FS::cust_main::Billing:&lt;br /&gt;
* _handle_taxes: Identify the tax definitions that apply to this invoice, and make a list of all sale line items that are subject to each tax definition. This also finds any exemptions that apply.&lt;br /&gt;
* calculate_taxes: For each tax definition, calculate its tax on the set of taxable items it applies to. Create an allocation record for each of them. Then consolidate those records to make a single tax line item for each ''tax name''. Insert those records and the allocation records.&lt;br /&gt;
&lt;br /&gt;
All of this was done before ''any'' of the records were inserted, which involved building a large free-floating data structure with references from tax line items to allocation records to taxable items to exemption records. FS::cust_bill_pkg::insert would then traverse this structure and insert the records, turning in-memory references into foreign keys.&lt;br /&gt;
&lt;br /&gt;
In 4.x this is somewhat simpler: FS::cust_main::Billing inserts the invoice, along with all its package line items, then calls FS::TaxEngine::calculate_taxes to get a list of tax line items. It then inserts all of them and adds the tax amount to cust_bill.charged. This was needed to support batch mode, since the invoice may sit for the rest of the day before being submitted for tax calculation. Before the taxes have been added, the invoice has the 'pending' flag set so that we know it's not finished.&lt;br /&gt;
&lt;br /&gt;
'''tax_data_vendor''' selects the tax service to use. If it's a batch-mode service (i.e. billsoft), freeside-daily calls FS::TaxEngine::transfer_batch, which takes all pending invoices and sends them in for batch calculation, then appends the resulting taxes to the invoices and marks them as non-pending.&lt;br /&gt;
&lt;br /&gt;
== Schema ==&lt;br /&gt;
&lt;br /&gt;
=== For internal taxes ===&lt;br /&gt;
&lt;br /&gt;
Tax rates are defined in cust_main_county, which has the following information:&lt;br /&gt;
* The tax rate (&amp;quot;tax&amp;quot;), as a percentage.&lt;br /&gt;
* The tax name.&lt;br /&gt;
* Where the tax applies: country, state, county, city, and district. Except country, they can be null.&lt;br /&gt;
* What the tax applies to: a &amp;quot;taxclass&amp;quot; string, and &amp;quot;setuptax&amp;quot; and &amp;quot;recurtax&amp;quot; flags indicating that setup / recur charges are exempt. (Yes, the names are confusing.)&lt;br /&gt;
* &amp;quot;exempt_amount&amp;quot;: the per-customer monthly exemption if any.&lt;br /&gt;
* &amp;quot;source&amp;quot;: keeps track of which tax rates are automatically maintained. Currently only applies to the Washington State Sales Tax.&lt;br /&gt;
&lt;br /&gt;
Tax allocation records are in cust_bill_pkg_tax_location, and contain:&lt;br /&gt;
* Foreign keys linking to the cust_bill_pkg records of both the tax and the taxed sale.&lt;br /&gt;
* A foreign key to the cust_main_county record defining the tax.&lt;br /&gt;
* The amount of tax.&lt;br /&gt;
* &amp;quot;taxtype&amp;quot;, always 'FS::cust_main_county' for now.&lt;br /&gt;
* &amp;quot;pkgnum&amp;quot; and &amp;quot;locationnum&amp;quot; fields, which are now redundant, since the taxed sale is already linked back to the package.&lt;br /&gt;
&lt;br /&gt;
Exempt sale records are in cust_tax_exempt_pkg. One exempt sale record is created for each tax that the sale was exempted from. They contain:&lt;br /&gt;
* The billpkgnum of the sale.&lt;br /&gt;
* The cust_main_county record (taxnum) it was exempted from.&lt;br /&gt;
* The year and month that the exemption occurred. This is to make monthly exemptions easier to calculate.&lt;br /&gt;
* The sale amount that was exempted.&lt;br /&gt;
* Several &amp;quot;exempt_&amp;quot; flags indicating why the sale was exempt: because the customer is tax-exempt (&amp;quot;cust&amp;quot;) or selectively exempt (&amp;quot;cust_taxname&amp;quot;), or the tax doesn't apply to setup / recur fees, or there's a monthly exemption.&lt;br /&gt;
* A foreign key to cust_credit_bill_pkg. This is used for negative exemption records created when a credit is applied to a tax-exempt sale. For &amp;quot;normal&amp;quot; exempt sale records it will be null.&lt;br /&gt;
&lt;br /&gt;
Package tax classes are in the part_pkg.taxclass field. This is a string, not a foreign key, though there's a list of allowed tax classes in part_pkg_taxclass.&lt;br /&gt;
&lt;br /&gt;
=== For external/vendor taxes ===&lt;br /&gt;
&lt;br /&gt;
Most tables related to external taxes have a &amp;quot;data_vendor&amp;quot; field which identifies which tax service vendor the record came from / applies to.&lt;br /&gt;
&lt;br /&gt;
Tax rates are defined in tax_rate. For CCH taxes, all fields in tax_rate are used for the tax calculation. For Suretax / Avalara taxes, we still create a record for each tax name + jurisdiction as the tax service tells us about them, but the other fields are left empty. The essential fields are:&lt;br /&gt;
* The tax name.&lt;br /&gt;
* data_vendor&lt;br /&gt;
* Where the tax applies, in terms of the &amp;quot;geocode&amp;quot; field, which links to tax_rate_location.&lt;br /&gt;
* What kind of tax it is: &amp;quot;taxclassnum&amp;quot;, a foreign key to tax_class.&lt;br /&gt;
&lt;br /&gt;
The allocation records are in cust_bill_pkg_tax_rate_location and are pretty similar to the ones for internal taxes, except that they also have &amp;quot;taxratelocationnum&amp;quot;, a link to tax_rate_location.&lt;br /&gt;
&lt;br /&gt;
External taxes have a more complex system for classifying the taxability of packages. &amp;quot;Tax products&amp;quot; are defined by the vendor, and stored locally in part_pkg_taxproduct:&lt;br /&gt;
* &amp;quot;taxproduct&amp;quot; is the vendor's code for this product.&lt;br /&gt;
* &amp;quot;description&amp;quot; is how it should look to the Freeside user.&lt;br /&gt;
&lt;br /&gt;
Each part_pkg can then be associated with a tax product via the taxproductnum field. However, a single package can generate several kinds of charges with different tax treatment. So there are also part_pkg options named &amp;quot;usage_taxproductnum_FOO&amp;quot; where FOO = &amp;quot;setup&amp;quot;, &amp;quot;recur&amp;quot;, or a usage class number. This allows different taxes to apply to recurring and non-recurring fees, and to local, interstate, or international calls.&lt;br /&gt;
&lt;br /&gt;
Tax vendors also typically have their own coding scheme for customer locations. We refer to these codes as &amp;quot;geocode&amp;quot;. cust_location.geocode is the geocode of a package location. The geocodes are defined in tax_rate_location, which has these fields:&lt;br /&gt;
* data_vendor&lt;br /&gt;
* geocode&lt;br /&gt;
* country, state, county, city&lt;br /&gt;
&lt;br /&gt;
part_pkg_taxrate relates tax products to the taxes that apply to them in each geocoded region. This is the core of the CCH tax engine and is unused for any other tax system.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=9619</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=9619"/>
				<updated>2017-01-12T23:28:57Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Freeside ==&lt;br /&gt;
=== Versions ===&lt;br /&gt;
&lt;br /&gt;
:'''Maintenance Version:''' 3.91&lt;br /&gt;
::released Aug 10th, 2016&lt;br /&gt;
::v3.x will be end-of-life after Q4 2017&lt;br /&gt;
&lt;br /&gt;
:'''Current Version:''': 4.1&lt;br /&gt;
::released Aug 5, 2016&lt;br /&gt;
&lt;br /&gt;
:'''Future''': 5.x / git master&lt;br /&gt;
::2018&lt;br /&gt;
&lt;br /&gt;
=== Turn Key Solutions ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#install Installation]&lt;br /&gt;
*[http://www.freeside.biz/freeside/products.html Freeside Appliance]&lt;br /&gt;
&lt;br /&gt;
=== Documentation ===&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:2.3:Documentation|2.3 Documentation]]&lt;br /&gt;
* [[Freeside:3:Documentation|3.x Documentation]]&lt;br /&gt;
* [[Freeside:4:Documentation|4.x Documentation]]&lt;br /&gt;
* [[Invoice_template_documentation|Invoice template documentation]] (3.x and later)&lt;br /&gt;
&lt;br /&gt;
=== Support ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#support Freeside Internet Services Inc.]&lt;br /&gt;
*[[Freeside:Support:Consultants | Freeside Consultants]]&lt;br /&gt;
*[[Freeside:Support:HelpWanted | Help Wanted]]&lt;br /&gt;
&lt;br /&gt;
===Training===&lt;br /&gt;
*[[Training syllabus]]&lt;br /&gt;
&lt;br /&gt;
=== Specs in progress ===&lt;br /&gt;
* [[Use Cases]]&lt;br /&gt;
* [[Business::FraudDetect]]&lt;br /&gt;
* [[Event_Refactor]]&lt;br /&gt;
* [[UI_Refactor]]&lt;br /&gt;
* [[Website_Refactor]]&lt;br /&gt;
* [[Browser_support]]&lt;br /&gt;
* [[Test Suite]]&lt;br /&gt;
&lt;br /&gt;
=== Historical ===&lt;br /&gt;
* [[Batch_Refactor]]&lt;br /&gt;
* [[Broadband_Services_Spec]]&lt;br /&gt;
* [[Virtual_to_Real_Fields]]&lt;br /&gt;
* [[part_pkg Mixin Refactor]]&lt;br /&gt;
* A new [[Version_Control_System]]&lt;br /&gt;
* [[RPM_Build_system]]&lt;br /&gt;
&lt;br /&gt;
= WIKI Reference =&lt;br /&gt;
&lt;br /&gt;
[http://meta.wikimedia.org/wiki/Help:Editing How to edit pages (wiki markup, etc)]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://www.mediawiki.org/wiki/Help:Configuration_settings Configuration settings list]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://meta.wikipedia.org/wiki/MediaWiki_User%27s_Guide User's Guide]&lt;br /&gt;
&lt;br /&gt;
[[Sandbox]] &amp;amp;larr; Use this page to test out editing, [http://www.phrases.org.uk/meanings/225200.html learn the ropes], etc.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=9618</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=9618"/>
				<updated>2017-01-12T23:28:42Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: add link to invoice template doc&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Freeside ==&lt;br /&gt;
=== Versions ===&lt;br /&gt;
&lt;br /&gt;
:'''Maintenance Version:''' 3.91&lt;br /&gt;
::released Aug 10th, 2016&lt;br /&gt;
::v3.x will be end-of-life after Q4 2017&lt;br /&gt;
&lt;br /&gt;
:'''Current Version:''': 4.1&lt;br /&gt;
::released Aug 5, 2016&lt;br /&gt;
&lt;br /&gt;
:'''Future''': 5.x / git master&lt;br /&gt;
::2018&lt;br /&gt;
&lt;br /&gt;
=== Turn Key Solutions ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#install Installation]&lt;br /&gt;
*[http://www.freeside.biz/freeside/products.html Freeside Appliance]&lt;br /&gt;
&lt;br /&gt;
=== Documentation ===&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:2.3:Documentation|2.3 Documentation]]&lt;br /&gt;
* [[Freeside:3:Documentation|3.x Documentation]]&lt;br /&gt;
* [[Freeside:4:Documentation|4.x Documentation]]&lt;br /&gt;
* [[Invoice_template_documentation]] (3.x and later)&lt;br /&gt;
&lt;br /&gt;
=== Support ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#support Freeside Internet Services Inc.]&lt;br /&gt;
*[[Freeside:Support:Consultants | Freeside Consultants]]&lt;br /&gt;
*[[Freeside:Support:HelpWanted | Help Wanted]]&lt;br /&gt;
&lt;br /&gt;
===Training===&lt;br /&gt;
*[[Training syllabus]]&lt;br /&gt;
&lt;br /&gt;
=== Specs in progress ===&lt;br /&gt;
* [[Use Cases]]&lt;br /&gt;
* [[Business::FraudDetect]]&lt;br /&gt;
* [[Event_Refactor]]&lt;br /&gt;
* [[UI_Refactor]]&lt;br /&gt;
* [[Website_Refactor]]&lt;br /&gt;
* [[Browser_support]]&lt;br /&gt;
* [[Test Suite]]&lt;br /&gt;
&lt;br /&gt;
=== Historical ===&lt;br /&gt;
* [[Batch_Refactor]]&lt;br /&gt;
* [[Broadband_Services_Spec]]&lt;br /&gt;
* [[Virtual_to_Real_Fields]]&lt;br /&gt;
* [[part_pkg Mixin Refactor]]&lt;br /&gt;
* A new [[Version_Control_System]]&lt;br /&gt;
* [[RPM_Build_system]]&lt;br /&gt;
&lt;br /&gt;
= WIKI Reference =&lt;br /&gt;
&lt;br /&gt;
[http://meta.wikimedia.org/wiki/Help:Editing How to edit pages (wiki markup, etc)]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://www.mediawiki.org/wiki/Help:Configuration_settings Configuration settings list]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://meta.wikipedia.org/wiki/MediaWiki_User%27s_Guide User's Guide]&lt;br /&gt;
&lt;br /&gt;
[[Sandbox]] &amp;amp;larr; Use this page to test out editing, [http://www.phrases.org.uk/meanings/225200.html learn the ropes], etc.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9617</id>
		<title>Invoice template documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9617"/>
				<updated>2017-01-11T04:28:42Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Style cleanup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= General notes =&lt;br /&gt;
Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template.&lt;br /&gt;
&lt;br /&gt;
The main function for generating the LaTeX code is FS::Template_Mixin::print_generic. This is a method of the cust_bill object (and also of quotation objects), and takes several arguments described in the perldoc. Most of what this function does is set up the &amp;quot;%invoice_data&amp;quot; hash, which gets imported into local variable space when evaluating the template.&lt;br /&gt;
&lt;br /&gt;
print_generic() actually constructs several templates: 'notes', 'coupon', 'footer', 'smallfooter', 'watermark', and sometimes 'summary'. Exactly which config variable is loaded for each of these is decided in Template_Mixin; look for the line that starts &amp;quot;my $tc&amp;quot;. It's '''invoice_$format$part''' (where $format is either &amp;quot;latex&amp;quot; or &amp;quot;html&amp;quot; and $part is &amp;quot;notes&amp;quot;, &amp;quot;coupon&amp;quot;, etc.). Each template is evaluated, with %invoice_data as arguments, then put back into %invoice_data as &amp;quot;$notes&amp;quot;, &amp;quot;$coupon&amp;quot;, etc. The main template contains code fragments to include them.&lt;br /&gt;
&lt;br /&gt;
To further complicate things, all of these elements have locale overrides AND invoice mode overrides. If print_generic() was called with a 'mode' argument, those templates will be fetched from the invoice mode + customer locale. If not, they'll be taken from the system config for the customer's agent and locale.&lt;br /&gt;
&lt;br /&gt;
You can obtain the generated LaTeX code for a particular invoice with ''view/cust_bill-tex.cgi'', which takes all the same parameters as ''view/cust_bill.cgi''.&lt;br /&gt;
&lt;br /&gt;
The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside.&lt;br /&gt;
&lt;br /&gt;
One useful tool for editing a template is Overleaf ([https://www.overleaf.com/ https://www.overleaf.com/]), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of &amp;quot;edit template, set config, download and examine PDF&amp;quot;, especially since it will display LaTeX errors in a semi-useful way.&lt;br /&gt;
&lt;br /&gt;
== Positioning ==&lt;br /&gt;
This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the &amp;quot;textpos&amp;quot; package.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;textpos&amp;quot; requires you to set up a grid system, like this:&lt;br /&gt;
&lt;br /&gt;
 \usepackage[absolute]{textpos}&lt;br /&gt;
 \setlength{\TPHorizModule}{1in}&lt;br /&gt;
 \setlength{\TPVertModule}{1in}&lt;br /&gt;
&lt;br /&gt;
The environment for a fixed position on the page is &amp;quot;textblock&amp;quot;. The syntax is odd; notice parentheses rather than braces, and numbers without units (because the \setlength commands defined the units).&lt;br /&gt;
&lt;br /&gt;
 \begin{textblock}{3.25}(0.625, 0.5)&lt;br /&gt;
   \footnotesize{&lt;br /&gt;
     This text is in a box starting 0.625in left and 0.5in down from the page&lt;br /&gt;
     origin, in a box 3.25in wide. It will fit inside an envelope window.&lt;br /&gt;
   }&lt;br /&gt;
 \end{textblock}&lt;br /&gt;
&lt;br /&gt;
The textblock behaves a lot like a minipage, and can contain almost anything; in particular, \includegraphics works. It's positioned after everything else on the page, which means there's no protection against overprinting anything.&lt;br /&gt;
&lt;br /&gt;
== The standard invoice template ==&lt;br /&gt;
=== Preamble ===&lt;br /&gt;
Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document.&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;fancyhdr&amp;quot; supplies the \fancyhead and \fancyfoot commands to place left, right, and center headers and footers.&lt;br /&gt;
* &amp;quot;lastpage&amp;quot; creates the LastPage reference for displaying &amp;quot;page X of Y&amp;quot; labels.&lt;br /&gt;
* &amp;quot;ifthen&amp;quot; provides the \ifthenelse command.&lt;br /&gt;
* &amp;quot;array&amp;quot; provides support for custom table column formats.&lt;br /&gt;
* &amp;quot;longtable&amp;quot; provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the &amp;quot;\LTextracouponspace&amp;quot; variable.&lt;br /&gt;
* &amp;quot;afterpage&amp;quot; was removed from the template in May 2005 and we can remove the package at some point.&lt;br /&gt;
* &amp;quot;multirow&amp;quot; is used to make a table cell in the coupon to hold the return address.&lt;br /&gt;
* &amp;quot;bigstrut&amp;quot; appears not to be used.&lt;br /&gt;
* &amp;quot;truncate&amp;quot; provides a command to apply a width limit to a line of text, so that anything past that gets replaced with an ellipsis. This is highly encouraged for things like package descriptions that could otherwise overflow their table cells.&lt;br /&gt;
* &amp;quot;graphicx&amp;quot; provides the \includegraphics command for inserting the EPS of the logo.&lt;br /&gt;
* &amp;quot;inputenc&amp;quot; and &amp;quot;fontenc&amp;quot; make LaTeX play nicely with UTF-8 characters.&lt;br /&gt;
* &amp;quot;background&amp;quot; is used to create watermarks (such as are used for some past-due notices, or voided invoices). The \backgroundsetup command takes a TikZ argument list, which is extremely powerful. See &amp;quot;texdoc pgf&amp;quot; for all the horrifying details.&lt;br /&gt;
&lt;br /&gt;
Useful things to add here:&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;textpos&amp;quot;: see above.&lt;br /&gt;
* &amp;quot;geometry&amp;quot; to manage the margins and headers in a centralized way. It also takes the [showframe] option, which will draw lines to visualize the page layout.&lt;br /&gt;
* &amp;quot;xcolor&amp;quot; if you want color.&lt;br /&gt;
* &amp;quot;tabularx&amp;quot; and/or &amp;quot;tabulary&amp;quot; add more options for setting table column widths.&lt;br /&gt;
&lt;br /&gt;
 \documentclass[letterpaper]{article}&lt;br /&gt;
&lt;br /&gt;
 \usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}&lt;br /&gt;
 \usepackage[breakwords]{truncate} % to avoid overflowing boxes&lt;br /&gt;
 \usepackage{graphicx}     % required for logo graphic&lt;br /&gt;
 \usepackage[utf8]{inputenc}             % multilanguage support&lt;br /&gt;
 \usepackage[T1]{fontenc}&lt;br /&gt;
 [@-- if ( length($watermark) ) {&lt;br /&gt;
   $OUT .= '&lt;br /&gt;
 \usepackage{background}&lt;br /&gt;
 \backgroundsetup{&lt;br /&gt;
   placement=center,&lt;br /&gt;
   opacity=0.25,&lt;br /&gt;
   color=black,&lt;br /&gt;
   angle=0,&lt;br /&gt;
   contents=' . $watermark . '&lt;br /&gt;
 }';&lt;br /&gt;
 }&lt;br /&gt;
 ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Set up dimensions. Most of these were determined empirically and really ought to use the &amp;quot;geometry&amp;quot; package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight.&lt;br /&gt;
&lt;br /&gt;
See [https://en.wikibooks.org/wiki/LaTeX/Page_Layout LaTeX Page Layout] and [http://mirrors.ctan.org/macros/latex/base/letter.pdf texdoc letter] for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that.&lt;br /&gt;
&lt;br /&gt;
If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container.&lt;br /&gt;
&lt;br /&gt;
Commands for manipulating length variables: \newlength declares them (and all of these are predeclared in the document stylesheet); \setlength sets them to a value; \addtolength adds to them.&lt;br /&gt;
&lt;br /&gt;
If you're adjusting margins, it can be helpful to add &amp;quot;\usepackage{layout}&amp;quot; to the preamble, and then &amp;quot;\newpage\layout&amp;quot; before the final &amp;quot;\end{document}&amp;quot;. This will print a diagram showing the layout boxes and their dimensions.&lt;br /&gt;
&lt;br /&gt;
\LTchunksize is an internal variable in longtable for how many rows to process at a time. There's probably no reason to change it.&lt;br /&gt;
&lt;br /&gt;
 \addtolength{\voffset}{-0.0cm}    % top margin to top of header&lt;br /&gt;
 \addtolength{\hoffset}{-0.6cm}    % left margin on page&lt;br /&gt;
 \addtolength{\topmargin}{[@-- defined($topmargin) ? $topmargin : '-1.00cm' --@]}&lt;br /&gt;
 \setlength{\headheight}{2.0cm}    % height of header&lt;br /&gt;
 \setlength{\headsep}{[@-- defined($headsep) ? $headsep : '1.0cm' --@]}&lt;br /&gt;
 \setlength{\footskip}{1.0cm}    % bottom of footer from bottom of text&lt;br /&gt;
&lt;br /&gt;
 %\addtolength{\textwidth}{2.1in}      % width of text&lt;br /&gt;
 \setlength{\textwidth}{19.5cm}&lt;br /&gt;
 \setlength{\textheight}{[@-- defined($textheight) ? $textheight : '19.5cm' --@]}&lt;br /&gt;
 \setlength{\oddsidemargin}{-0.9cm}  % odd page left margin&lt;br /&gt;
 \setlength{\evensidemargin}{-0.9cm}   % even page left margin&lt;br /&gt;
&lt;br /&gt;
 \LTchunksize=40&lt;br /&gt;
&lt;br /&gt;
$coupon is a local variable containing the entire generated content of the [#Coupon_template invoice payment coupon]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string.&lt;br /&gt;
&lt;br /&gt;
\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position).&lt;br /&gt;
&lt;br /&gt;
\extracouponspace is a config setting for how much space to reserve for the coupon. We will &amp;quot;shorten&amp;quot; the first page's content by that much, which moves the starting position of the header up.&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\headrulewidth}{0pt}&lt;br /&gt;
 \renewcommand{\footrulewidth}{1pt}&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\footrule}{&lt;br /&gt;
 [@--&lt;br /&gt;
   $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
 --@]&lt;br /&gt;
   {&lt;br /&gt;
   }&lt;br /&gt;
   {&lt;br /&gt;
     \vbox to 0pt{\rule{\headwidth}{\footrulewidth}\vss}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
 \newcommand{\extracouponspace}{[@-- defined($extracouponspace) ? $extracouponspace : '2.7in' --@]}&lt;br /&gt;
&lt;br /&gt;
Positioning mailing addresses. In current code I'd recommend using the &amp;quot;textpos&amp;quot; package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout.&lt;br /&gt;
&lt;br /&gt;
 % Adjust the inset of the mailing address&lt;br /&gt;
 \newcommand{\addressinset}[1][]{\hspace{1.0cm}}&lt;br /&gt;
&lt;br /&gt;
 % Adjust the inset of the return address and logo&lt;br /&gt;
 \newcommand{\returninset}[1][]{\hspace{-0.25cm}}&lt;br /&gt;
&lt;br /&gt;
 % New command for address lines i.e. skip them if blank&lt;br /&gt;
 \newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}&lt;br /&gt;
&lt;br /&gt;
 % Inserts dollar symbol&lt;br /&gt;
 \newcommand{\dollar}[1][]{\symbol{36}}&lt;br /&gt;
&lt;br /&gt;
=== Headers/footers ===&lt;br /&gt;
&amp;quot;fancyhdr&amp;quot; allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer.&lt;br /&gt;
&lt;br /&gt;
These commands are supposed to remove the default &amp;quot;plain&amp;quot; style page footer, but we don't use that page style so it's probably not needed.&lt;br /&gt;
&lt;br /&gt;
 % Remove plain style header/footer&lt;br /&gt;
 \fancypagestyle{plain}{&lt;br /&gt;
   \fancyhead{}&lt;br /&gt;
 }&lt;br /&gt;
 \fancyhf{}&lt;br /&gt;
&lt;br /&gt;
The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position.&lt;br /&gt;
&lt;br /&gt;
The footer to display below the coupon (or if there isn't one) is configurable as '''invoice_latexfooter'''. The footer to show on subsequent pages is '''invoice_latexsmallfooter'''. By default they're both just the company name.&lt;br /&gt;
&lt;br /&gt;
 % Define fancy header/footer for first and subsequent pages&lt;br /&gt;
 \fancyfoot[C]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($coupon) {&lt;br /&gt;
     $OUT .= '\vspace{-\extracouponspace}';&lt;br /&gt;
     $OUT .= '\rule[0.5em]{\textwidth}{\footrulewidth}\\\\';&lt;br /&gt;
     $OUT .= $coupon;&lt;br /&gt;
     $OUT .= '\vspace{'.&lt;br /&gt;
       (defined($couponfootsep) ? $couponfootsep : '0.2in') .&lt;br /&gt;
       '}';&lt;br /&gt;
   }&lt;br /&gt;
   ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
 --@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]&lt;br /&gt;
 [@-- $footer --@]&lt;br /&gt;
     }[@-- $coupon ? '\vspace{\extracouponspace}' : '' --@]&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]&lt;br /&gt;
 [@-- $smallfooter --@]&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side footer, showing the page number. &amp;quot;emt()&amp;quot; localizes text to the customer's language and then escapes it for LaTeX. &amp;quot;~&amp;quot; is a non-breaking space character. It's suppressed on the first page due to the coupon.&lt;br /&gt;
&lt;br /&gt;
 \fancyfoot[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     \small{\thepage~[@-- emt('of') --@]~\pageref{LastPage}}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The left side header, containing the return address and logo on the first page (and nothing on later pages). \returninset is just a horizontal adjustment. \makebox{} tells LaTeX to put its contents into a single box. That's probably unnecessary because the only thing in the box is a table, which is already a single box.&lt;br /&gt;
&lt;br /&gt;
\begin{tabular}{ll} creates a two-column table. In the first column is a &amp;quot;minipage&amp;quot;, which is just a fixed-width container environment (5.5cm here) with its bottom edge aligned to the table cell it's in. In here we put the evaluated &amp;quot;returnaddress&amp;quot; template.&lt;br /&gt;
&lt;br /&gt;
Tables in LaTeX always use &amp;amp; to separate columns and \\ to separate rows. A row must always contain as many columns as in the column spec; otherwise LaTeX will guess about what you mean and will usually get it wrong. \\ at the end of the last row is optional.&lt;br /&gt;
&lt;br /&gt;
The second column contains the logo graphic. See &amp;quot;texdoc graphicx&amp;quot; for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: &amp;quot;\includegraphics[height=0.5in]{[@-- $logo_file --@]}&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
 \fancyhead[L]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
     \returninset&lt;br /&gt;
     \makebox{&lt;br /&gt;
       \begin{tabular}{ll}&lt;br /&gt;
         \begin{minipage}[b]{5.5cm}&lt;br /&gt;
 [@-- $returnaddress --@]&lt;br /&gt;
         \end{minipage} &amp;amp;&lt;br /&gt;
         \includegraphics{[@-- $logo_file --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     %\includegraphics{[@-- $logo_file --@]} % Uncomment if you want the logo on all pages.&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side header, containing the invoice date, invnum, and custnum in a three-column table with each column centered ({ccc}). &amp;quot;$no_date&amp;quot; and &amp;quot;$no_number&amp;quot; are both enabled by arguments to print_generic() and used when printing statements.&lt;br /&gt;
&lt;br /&gt;
Note that &amp;quot;$date&amp;quot; is already formatted using time2str_local('long'), so something like &amp;quot;Feb 1st, 2017&amp;quot;. The template can call time2str() to format other dates; this is an alias to time2str_local so it will use the customer's language settings and LaTeX-escape the result.&lt;br /&gt;
&lt;br /&gt;
This is followed by a horizontal line spanning the table (\hline), then a row where the middle cell contains the $notice_name (usually &amp;quot;Invoice&amp;quot;) in \huge font, in small caps (\textsc). The \rule{0pt}{5ex} creates a vertical strut (a zero-width rule) which increases the row height to 5ex. Then another \hline.&lt;br /&gt;
&lt;br /&gt;
Note that \hline should never be followed by \\ and will cause errors if it is.&lt;br /&gt;
&lt;br /&gt;
On subsequent pages we show a smaller version without the notice name.&lt;br /&gt;
&lt;br /&gt;
 \fancyhead[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
     \begin{tabular}{ccc}&lt;br /&gt;
     [@-- join(' &amp;amp; ', ( $no_date   ?  '': emt('Invoice date') ),''&lt;br /&gt;
                      ( $no_number ?  '': emt('Invoice #')    ),''&lt;br /&gt;
                      emt('Customer #')&lt;br /&gt;
              )&lt;br /&gt;
     --@]\\&lt;br /&gt;
     \vspace{0.2cm}&lt;br /&gt;
     \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]} \\\hline&lt;br /&gt;
     \rule{0pt}{5ex} &amp;amp;~~ \huge{\textsc{[@-- emt($notice_name) --@]}} &amp;amp; \\&lt;br /&gt;
     \vspace{-0.2cm}&lt;br /&gt;
      &amp;amp; &amp;amp; \\\hline&lt;br /&gt;
     \end{tabular}&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     \small{&lt;br /&gt;
       \begin{tabular}{lll}&lt;br /&gt;
       [@-- join(' &amp;amp; ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\&lt;br /&gt;
       \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Enable the fancyhdr page style and set the font family.&lt;br /&gt;
&lt;br /&gt;
 \pagestyle{fancy}&lt;br /&gt;
 %% Font options are:&lt;br /&gt;
 %%  bch Bitsream Charter&lt;br /&gt;
 %%  put Utopia&lt;br /&gt;
 %%  phv Adobe Helvetica&lt;br /&gt;
 %%  pnc New Century Schoolbook&lt;br /&gt;
 %%  ptm Times&lt;br /&gt;
 %%  pcr Courier&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\familydefault}{phv}&lt;br /&gt;
&lt;br /&gt;
=== Line item table macros ===&lt;br /&gt;
This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are:&lt;br /&gt;
&lt;br /&gt;
* Package number&lt;br /&gt;
* Description (6 columns)&lt;br /&gt;
* Price&lt;br /&gt;
&lt;br /&gt;
(The 6 columns are used separately for showing CDRs, if there are any.)&lt;br /&gt;
&lt;br /&gt;
If '''invoice-unitprice''' is on, it's:&lt;br /&gt;
&lt;br /&gt;
* Package number&lt;br /&gt;
* Description (4 columns)&lt;br /&gt;
* Unit price&lt;br /&gt;
* Quantity&lt;br /&gt;
* Price&lt;br /&gt;
&lt;br /&gt;
According to that setting, create macros for how many columns and how much space to allocate to the description, and for the headers of the Unit Price and Unit Quantity columns.&lt;br /&gt;
&lt;br /&gt;
 % Commands for freeside table header...&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }&lt;br /&gt;
 \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }&lt;br /&gt;
 \newcommand{\FSunitcolumns}{ [@--&lt;br /&gt;
   $unitprices&lt;br /&gt;
   ? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &amp;amp;' .&lt;br /&gt;
     '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} &amp;amp; '&lt;br /&gt;
   :  ''--@] }''&lt;br /&gt;
&lt;br /&gt;
\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c).&lt;br /&gt;
&lt;br /&gt;
\truncate renders its argument into a space with a maximum width. It will break at word boundaries and insert an ellipsis afterward. Note that if we didn't do this, longtable would either squish the surrounding columns or push them off the page rather than word-wrap the description.&lt;br /&gt;
&lt;br /&gt;
\FSusagehead is the same, but used for usage summary sections that show the number of calls and total minutes. The '''usage_class_summary''' config turns this on.&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FShead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{\FSdescriptioncolumncount}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \FSunitcolumns&lt;br /&gt;
   \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FSusagehead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Calls') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Duration') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Amount') --@]} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Next, the commands to actually display line items.&lt;br /&gt;
&lt;br /&gt;
\FSdesc will be called once for each element in the @detail_items array. This takes five arguments (that's the [5] in the declaration) which are the values to put in the pkgnum, description, unit price, quantity, and price columns. If unit prices are off then arguments 3 and 4 will be empty.&lt;br /&gt;
&lt;br /&gt;
\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font.&lt;br /&gt;
&lt;br /&gt;
\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() into LaTeX table rows (pre-escaped, delimited with &amp;amp;). So, the macro just skips the first column and outputs its argument.&lt;br /&gt;
&lt;br /&gt;
\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column.&lt;br /&gt;
&lt;br /&gt;
Finally, \FSusagedesc is for usage summary sections, and puts the total calls, duration, and amount in the correct columns.&lt;br /&gt;
&lt;br /&gt;
 % ...description...&lt;br /&gt;
 \newcommand{\FSdesc}[5]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{[@-- $unitprices ? '4' : '6' --@]}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{#2}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
 [@-- $unitprices ? '  \multicolumn{1}{r}{\textbf{#3}} &amp;amp;'.&amp;quot;\n&amp;quot;.&lt;br /&gt;
                    '  \multicolumn{1}{r}{\textbf{#4}} &amp;amp;'.&amp;quot;\n&amp;quot;&lt;br /&gt;
                  :&lt;br /&gt;
 --@]&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#5}}\\&lt;br /&gt;
 }&lt;br /&gt;
 % ...extended description...&lt;br /&gt;
 \newcommand{\FSextdesc}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{\small{[[User:Mark|Mark]] ([[User talk:Mark|talk]])&amp;lt;nowiki&amp;gt;#1}}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   } \\&lt;br /&gt;
 }&lt;br /&gt;
 % ...call detail (multiple columns already)...&lt;br /&gt;
 \newcommand{\FScalldetail}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   [[User:Mark|Mark]] ([[User talk:Mark|talk]])&amp;lt;nowiki&amp;gt;#1&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
 }&lt;br /&gt;
 % ...and total line items (which use the full 12.8cm length, ignoring&lt;br /&gt;
 % unitprice/quantity&lt;br /&gt;
 \newcommand{\FStotaldesc}[2]{&lt;br /&gt;
   &amp;amp; \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{#1}&lt;br /&gt;
   } &amp;amp; #2\\&lt;br /&gt;
 }&lt;br /&gt;
 % ...usage class summary&lt;br /&gt;
 \newcommand{\FSusagedesc}[4]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#2}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#3}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#4}}&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Main document ===&lt;br /&gt;
First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line.&lt;br /&gt;
&lt;br /&gt;
 \begin{document}&lt;br /&gt;
 % Headers and footers defined for the first page&lt;br /&gt;
 \addressinset \rule{0.5cm}{0cm}&lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{7.0cm}&lt;br /&gt;
 \vspace{[@-- defined($addresssep) ? $addresssep : '0.25cm' --@]}&lt;br /&gt;
 \textbf{[@-- $payname --@]}\\&lt;br /&gt;
 \addressline{[@-- $company --@]}&lt;br /&gt;
 \addressline{[@-- $address1 --@]}&lt;br /&gt;
 \addressline{[@-- $address2 --@]}&lt;br /&gt;
 \addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}&lt;br /&gt;
 \addressline{[@-- $country --@]}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
&lt;br /&gt;
Make another minipage for the service address, terms, and PO number. $ship_enable is true if the global '''invoice-ship_address''' or per-customer &amp;quot;invoice_ship_address&amp;quot; flag is on.&lt;br /&gt;
&lt;br /&gt;
The initial \hfill spreads out the two minipages so that they go all the way to the margins. The 'terms' and 'po_line' should probably be in \addressline{} macros but currently aren't.&lt;br /&gt;
&lt;br /&gt;
After that, skip some vertical space. (If you need more vertical space for an invoice format, the 1.5cm here can be cut.)&lt;br /&gt;
&lt;br /&gt;
 \hfill&lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{6.4cm}&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($ship_enable) {&lt;br /&gt;
     $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_company}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address1}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address2}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_city, $ship_state~~$ship_zip}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_country}&amp;quot;;&lt;br /&gt;
     $OUT .= '~\\\\';&lt;br /&gt;
   }else{&lt;br /&gt;
     $OUT .= ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
 \begin{flushright}&lt;br /&gt;
 [@-- $terms ? emt('Terms') . ': ' . emt($terms) :  ''--@]\\''&lt;br /&gt;
 [@-- $po_line --@]\\&lt;br /&gt;
 \end{flushright}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
 \vspace{1.5cm}&lt;br /&gt;
&lt;br /&gt;
Insert the [http://www.freeside.biz/mediawiki/index.php?title=Summary&amp;amp;action=edit&amp;amp;redlink=1 Summary] if there is one. The last thing in the summary template is a page break.&lt;br /&gt;
&lt;br /&gt;
 %&lt;br /&gt;
 [@-- $summary --@]&lt;br /&gt;
 %&lt;br /&gt;
&lt;br /&gt;
=== Sections ===&lt;br /&gt;
Start creating sections, according to the @sections array. Section entries contain the following keys:&lt;br /&gt;
&lt;br /&gt;
* description&lt;br /&gt;
* category (pkg_category.categoryname, for package sections by category)&lt;br /&gt;
* location (hashref of location fields, for package sections by location)&lt;br /&gt;
* pretotal (formatted with \FStotaldesc, before detail lines)&lt;br /&gt;
* subtotal (formatted with \FStotaldesc, at the end)&lt;br /&gt;
* posttotal (line to display after the end of the section; used for &amp;quot;Balance Due&amp;quot; message and a few other things)&lt;br /&gt;
* sort_weight (sort order, but also does complicated things)&lt;br /&gt;
&lt;br /&gt;
and optionally some flags:&lt;br /&gt;
&lt;br /&gt;
* post_total (put the section at the end of the invoice)&lt;br /&gt;
* summarized (omit the section entirely)&lt;br /&gt;
* usage_section (use \FSusagedesc instead of \FSdesc, see above)&lt;br /&gt;
* no_subtotal (omit the subtotal line)&lt;br /&gt;
&lt;br /&gt;
If '''invoice_sections''' is enabled: the $multisection flag in print_generic() is turned on, and @sections is filled by calling FS::Template_Mixin::_items_sections, which makes a section for each package category. Line items for packages go into the package category's section. '''invoice_sections_method''' can be used to divide sections by location rather than by category. In addition, sections are created for previous invoices, taxes, and &amp;quot;adjustments&amp;quot; (credits and payments to this invoice).&lt;br /&gt;
&lt;br /&gt;
If not: a single section is created, with empty description, containing previous invoices, current charges, and adjustments (after the total). '''previous_balance-section''' will make a previous invoices section anyway.&lt;br /&gt;
&lt;br /&gt;
Additional sections are created in some cases. '''svc_phone_sections''' creates a section for each phone line (see RT#6592; note that this customer is no longer active). '''voip-cust_accountcode_cdr''' creates a section for each accountcode (see RT#12181). '''discount-show_available''' creates a section with information on available discounts. '''usage_class_summary''' creates a section with usage subtotals.&lt;br /&gt;
&lt;br /&gt;
'''finance_pkgclass''' specifies the package class containing &amp;quot;finance charges&amp;quot;, which is handled specially. That class's category name is passed to the template as $finance_section, and the sum of charges in that category is passed as $finance_amount. That amount is broken out from the &amp;quot;Current charges&amp;quot; line in the summary section. On a multisection summary-format invoice, the finance charges will also ''not'' be shown in the body of the invoice. I don't know why.&lt;br /&gt;
&lt;br /&gt;
Loop over all sections (except for the finance charges, maybe). Inside the loop we'll do everything by appending to $OUT.&lt;br /&gt;
&lt;br /&gt;
 \section*{}&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section ( grep { !$summary || $_-&amp;gt;{description} ne $finance_section } @sections ) {&lt;br /&gt;
&lt;br /&gt;
We're going to do many things if &amp;quot;!$summary&amp;quot;. If there's a summary section, the pretotals and posttotals are redundant so skip them.&lt;br /&gt;
&lt;br /&gt;
    if ($section-&amp;gt;{'pretotal'} &amp;amp;&amp;amp; !$summary) {&lt;br /&gt;
      $OUT .= '\begin{flushright}';&lt;br /&gt;
      $OUT .= '\large\textsc{'. $section-&amp;gt;{'pretotal'}. '}\\\\';&lt;br /&gt;
      $OUT .= '\\end{flushright}';&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
Start a new page if this is a &amp;quot;post_total&amp;quot; section. As far as I know these are used only for usage details. I think the 'summarized' flag is no longer used for anything.&lt;br /&gt;
&lt;br /&gt;
The section heading is going to be a caption placed inside the main table. \captionsetup sets properties for the caption (left justified, bold, small caps, Large size). If this is the first page, also tell longtable (via \LTextracouponspace) to shorten the effective length of the page so that it doesn't overprint the coupon.&lt;br /&gt;
&lt;br /&gt;
    $OUT .= '\pagebreak' if $section-&amp;gt;{'post_total'};&lt;br /&gt;
    unless ($section-&amp;gt;{'summarized'} ) {&lt;br /&gt;
      $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';&lt;br /&gt;
      $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'&lt;br /&gt;
        if $coupon;&lt;br /&gt;
&lt;br /&gt;
Start the longtable that will contain the section's line items. Eight columns, pkgnum (centered), description and optional unitprice/quantity (left), then price (right).&lt;br /&gt;
&lt;br /&gt;
\caption* produces a table caption without prefixing it with &amp;quot;Table 1&amp;quot;. The caption is the section description, or 'Charges' if it's null (except that if packages are sectioned by location, we put one together from the location fields).&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\begin{longtable}{cllllllr}';&lt;br /&gt;
      $OUT .= '\caption*{ ';&lt;br /&gt;
      if ($section-&amp;gt;{'location'}) {&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'label_prefix'}. ': '&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'label_prefix'});&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'address1'};&lt;br /&gt;
        $OUT .= ', ' . $section-&amp;gt;{'location'}{'address2'}&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'address2'});&lt;br /&gt;
        $OUT .= ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'city'} . ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'state'} . '~' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'zip'};&lt;br /&gt;
      } elsif ( $section-&amp;gt;{'description'} ) {&lt;br /&gt;
        $OUT .= ($section-&amp;gt;{'description'});&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= emt('Charges');&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '}\\\\';&lt;br /&gt;
&lt;br /&gt;
The point of a longtable is that it can span multiple pages, including printing head/foot rows at the top/bottom of each page automatically. The protocol for setting these up is to specify the first head, per-page head, per-page foot, and last foot, then the rest of the table contents. Here, we set the first head to be \FShead (or \FSusagehead). \endfirsthead means we're done specifying it.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;header_generator&amp;quot; and other foo_generators are from an old mechanism that injected callbacks into the template data. This is strongly deprecated.&lt;br /&gt;
&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endfirsthead';&lt;br /&gt;
&lt;br /&gt;
The per-page head: a row with a &amp;quot;Continued&amp;quot; message, then \FShead again. The per-page foot is the same.&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endhead';&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';&lt;br /&gt;
      $OUT .= '\endfoot';&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
&lt;br /&gt;
The final foot for the section. For multisection invoices this contains the section subtotal:&lt;br /&gt;
&lt;br /&gt;
      if (scalar(@sections) &amp;gt; 1 and !$section-&amp;gt;{no_subtotal}) {&lt;br /&gt;
        if ($section-&amp;gt;{total_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{total_generator}}($section);&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FStotaldesc{' . $section-&amp;gt;{'description'} . ' Total}' .&lt;br /&gt;
                  '{' . $section-&amp;gt;{'subtotal'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
Single-section invoices use &amp;quot;@total_items&amp;quot; for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot.&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;nowiki&amp;gt;#if ($section == $sections[$#sections]) {&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
        foreach my $line (grep {$_-&amp;gt;{section}-&amp;gt;{description} eq $section-&amp;gt;{description}} @total_items) {&lt;br /&gt;
          if ($section-&amp;gt;{total_line_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{total_line_generator}}($line);&lt;br /&gt;
          } else {&lt;br /&gt;
            $OUT .= '\FStotaldesc{' . $line-&amp;gt;{'total_item'} . '}' .&lt;br /&gt;
                    '{' . $line-&amp;gt;{'total_amount'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      &amp;lt;nowiki&amp;gt;#}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
      $OUT .= '\endlastfoot';&lt;br /&gt;
&lt;br /&gt;
Line items are passed in @detail_items. A line item entry can contain:&lt;br /&gt;
&lt;br /&gt;
* section (reference)&lt;br /&gt;
* ref (pkgnum)&lt;br /&gt;
* description&lt;br /&gt;
* ext_description (arrayref of additional lines)&lt;br /&gt;
* quantity&lt;br /&gt;
* amount&lt;br /&gt;
* duration (for usage summary sections)&lt;br /&gt;
* pkgpart&lt;br /&gt;
* unit_amount (unit price)&lt;br /&gt;
* locationnum&lt;br /&gt;
* svc_label (the primary service's label)&lt;br /&gt;
&lt;br /&gt;
Line items are created in several places in Template_Mixin and TemplateItem_Mixin, most importantly _items_cust_bill_pkg.&lt;br /&gt;
&lt;br /&gt;
@detail_items is a single array; we find the lines belonging to this section by checking their 'section' element. If there's only one section, process all the lines.&lt;br /&gt;
&lt;br /&gt;
      my $lastref = 0;&lt;br /&gt;
      foreach my $line (&lt;br /&gt;
        grep { ( scalar( @sections ) &amp;gt; 1&lt;br /&gt;
               ? $section-&amp;gt;{'description'} eq $_-&amp;gt;{'section'}-&amp;gt;{'description'}&lt;br /&gt;
               : 1&lt;br /&gt;
             ) }&lt;br /&gt;
        @detail_items )&lt;br /&gt;
      {&lt;br /&gt;
        my $ext_description = $line-&amp;gt;{'ext_description'};&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;nowiki&amp;gt;# Don't break-up small packages.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
        my $rowbreak = @$ext_description &amp;lt; 5 ? '*' : ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
&lt;br /&gt;
$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum.&lt;br /&gt;
&lt;br /&gt;
If this is a usage summary section, convert the duration to minutes/seconds and call \FSusagedesc, passing the description, quantity (number of calls), duration, and total price.&lt;br /&gt;
&lt;br /&gt;
If it's a normal section, call \FSdesc, passing the description, unit price, quantity, and amount. If unit prices aren't in use, or there isn't one on this line item, then leave unit price and quantity blank.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;$rowbreak&amp;quot; is a flag for whether to allow a page break. In general we want to keep line items and their ext_description lines together, unless there are a lot of description lines. So if there are fewer than 5 of them, we set $rowbreak = '*', which after \FSdesc{} is evaluated, results in the table row delimiter being &amp;quot;\\*&amp;quot;. This tells LaTeX to avoid putting a page break immediately after this row. Probably we should set $rowbreak = '' on the last description line to explicitly allow page breaks between packages.&lt;br /&gt;
&lt;br /&gt;
        $OUT .= &amp;quot;\\hline\n&amp;quot; if (($line-&amp;gt;{'ref'} || 0) ne $lastref);&lt;br /&gt;
        if ($section-&amp;gt;{description_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{description_generator}}($line);&lt;br /&gt;
        } elsif ($section-&amp;gt;{usage_section}) {&lt;br /&gt;
          my $minutes = sprintf('%d', $line-&amp;gt;{'duration'} / 60);&lt;br /&gt;
          my $seconds = $line-&amp;gt;{'duration'} % 60;&lt;br /&gt;
          $OUT .= '\FSusagedesc&lt;br /&gt;
            {' . $line-&amp;gt;{'description'} . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'quantity'} . '}&lt;br /&gt;
            {' . $minutes . 'm ' . $seconds . 's' . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'amount'} . '}';&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FSdesc'.&lt;br /&gt;
                  '{}'.&lt;br /&gt;
                  '{' . $line-&amp;gt;{'description'} . '}' ;&lt;br /&gt;
          if ( $unitprices and length($line-&amp;gt;{'unit_amount'}) ) {&lt;br /&gt;
            &amp;lt;nowiki&amp;gt;# then show the unit amount and quantity&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
            $OUT .=&lt;br /&gt;
                '{\\dollar' . $line-&amp;gt;{'unit_amount'} . '}'.&lt;br /&gt;
                '{'         . $line-&amp;gt;{'quantity'}    . '}';&lt;br /&gt;
          } else {&lt;br /&gt;
            &amp;lt;nowiki&amp;gt;# leave those columns blank&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
            $OUT .= '{}{}';&lt;br /&gt;
          }&lt;br /&gt;
          $OUT .= '{\\dollar' . $line-&amp;gt;{'amount'} . &amp;quot;}${rowbreak}\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        $lastref = $line-&amp;gt;{'ref'} || 0;&lt;br /&gt;
&lt;br /&gt;
Output ext_description lines. We assume that any of them that contain an unescaped &amp;quot;&amp;amp;&amp;quot; are call details and should use \FScalldetail. Otherwise they use \FSextdesc.&lt;br /&gt;
&lt;br /&gt;
        foreach my $ext_desc (@$ext_description) {&lt;br /&gt;
          if ($section-&amp;gt;{extended_description_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{extended_description_generator}}($ext_desc);&lt;br /&gt;
          } elsif ( $ext_desc !~ /[^\\]&amp;amp;/ ) {&lt;br /&gt;
            $OUT .= '\FSextdesc{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          } else { # call detail&lt;br /&gt;
            $OUT .= '\FScalldetail{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
End the longtable. If there is a posttotal item in this section (the final Balance Due / Credit Balance message, previous invoice aging, a few other things) then show it right-justified, in the same formatting used for the caption.&lt;br /&gt;
&lt;br /&gt;
       $OUT .= '\end{longtable}';&lt;br /&gt;
     }&lt;br /&gt;
     if ($section-&amp;gt;{'posttotal'}) {&lt;br /&gt;
       $OUT .= '\begin{flushright}';&lt;br /&gt;
       $OUT .= '\normalfont\large\bfseries\textsc{'. $section-&amp;gt;{'posttotal'}. '}\\\\';&lt;br /&gt;
       $OUT .= '\\end{flushright}';&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.)&lt;br /&gt;
&lt;br /&gt;
There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space.&lt;br /&gt;
&lt;br /&gt;
However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the strut is at the bottom of the page, keeping the coupon space clear.&lt;br /&gt;
&lt;br /&gt;
 --@]&lt;br /&gt;
 \vfill&lt;br /&gt;
 \begin{minipage}[t]{\textwidth}&lt;br /&gt;
   [@-- length($summary)&lt;br /&gt;
          ?&lt;br /&gt;
         : ( $smallernotes&lt;br /&gt;
               ? '\scriptsize{ '.$notes.' }'&lt;br /&gt;
               : $notes&lt;br /&gt;
           )&lt;br /&gt;
   --@]&lt;br /&gt;
   [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' :  ''--@]''&lt;br /&gt;
 \end{minipage}&lt;br /&gt;
 \end{document}&lt;br /&gt;
&lt;br /&gt;
That's the end of the invoice.&lt;br /&gt;
&lt;br /&gt;
== The summary template ==&lt;br /&gt;
Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes.&lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{ll}&lt;br /&gt;
 \begin{minipage}{6.4cm}&lt;br /&gt;
 \begin{tabular}{m{0cm}m{6.4cm}}&lt;br /&gt;
 \rule{0cm}{10cm}&amp;amp;\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} &amp;amp;&lt;br /&gt;
&lt;br /&gt;
On the right side, place a 2cm horizontal strut followed by a 12.8cm minipage. This will contain the billing summary.&lt;br /&gt;
&lt;br /&gt;
 \rule{2cm}{0cm}&lt;br /&gt;
 \begin{minipage}{12.8cm}&lt;br /&gt;
&lt;br /&gt;
The billing summary itself is a two-column table, one for labels and one for amounts. The section headings are just labels with no amounts, with lines underneath.&lt;br /&gt;
&lt;br /&gt;
'true_previous_balance' is, as far as we can determine it, the balance that was printed on the customer's previous invoice. It's the sum of all the customer's transactions (invoices, credits, payments, refunds) dated before that invoice, plus the invoice amount.&lt;br /&gt;
&lt;br /&gt;
'balance_adjustments' is the sum of payments and credits received after the previous invoice, but applied to invoices before this current invoice. 'true_previous_balance' - 'balance_adjustments' is the total amount owed on all past invoices.&lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{lr}&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of Previous Balance and Payments}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{Previous Balance}&amp;amp;\textbf{\dollar[@-- $true_previous_balance --@]}\\&lt;br /&gt;
 \textbf{Payments}&amp;amp;\textbf{\dollar[@-- $balance_adjustments --@]}\\&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Balance Outstanding}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of New Charges}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
&lt;br /&gt;
@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. \cline draws a horizontal line (like \hline) but only under specified columns: here, only the second column. After that, make rows for the total new charges (minus finance charges), the total amount owed on previous invoices ('true_previous_balance' – 'balance_adjustments'), and the finance charges.&lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section (@summary_subtotals) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'} ? $section-&amp;gt;{'description'} : 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
   $OUT .= '\cline{2-2}';&lt;br /&gt;
 --@]&lt;br /&gt;
 \textbf{New Charges Total}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Invoice Summary}} &amp;amp; \\&lt;br /&gt;
 &amp;amp; \\&lt;br /&gt;
 \textbf{Previous Past Due Charges}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\&lt;br /&gt;
 \textbf{Finance charges on overdue amount}&amp;amp;\textbf{\dollar[@-- $finance_amount --@]}\\&lt;br /&gt;
 \textbf{New Charges}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
&lt;br /&gt;
Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the ''current'' invoice (not past invoices).&lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;#false laziness w/invoice_htmlsummary and above&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   foreach my $section ( grep $_-&amp;gt;{adjust_section}, @sections ) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'} ? $section-&amp;gt;{'description'} : 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Finally, the customer's balance including the current invoice, and then close the table and minipage, and force a page break so the line items can start on a new page.&lt;br /&gt;
&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Total Amount Due}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $balance) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} \\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \newpage&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Invoice modes ==&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9616</id>
		<title>Invoice template documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9616"/>
				<updated>2017-01-11T03:32:33Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= General notes  =&lt;br /&gt;
Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template. &lt;br /&gt;
&lt;br /&gt;
The main function for generating the LaTeX code is FS::Template_Mixin::print_generic. This is a method of the cust_bill object (and also of quotation objects), and takes several arguments described in the perldoc. Most of what this function does is set up the &amp;quot;%invoice_data&amp;quot; hash, which gets imported into local variable space when evaluating the template. &lt;br /&gt;
&lt;br /&gt;
print_generic() actually constructs several templates: 'notes', 'coupon', 'footer', 'smallfooter', 'watermark', and sometimes 'summary'. Exactly which config variable is loaded for each of these is decided in Template_Mixin; look for the line that starts &amp;quot;my $tc&amp;quot;. It's '''invoice_$format$part''' (where $format is either &amp;quot;latex&amp;quot; or &amp;quot;html&amp;quot; and $part is &amp;quot;notes&amp;quot;, &amp;quot;coupon&amp;quot;, etc.). Each template is evaluated, with&amp;amp;nbsp;%invoice_data as arguments, then put back into&amp;amp;nbsp;%invoice_data as &amp;quot;$notes&amp;quot;, &amp;quot;$coupon&amp;quot;, etc. The main template contains code fragments to include them. &lt;br /&gt;
&lt;br /&gt;
To further complicate things, all of these elements have locale overrides AND invoice mode overrides. If print_generic() was called with a 'mode' argument, those templates will be fetched from the invoice mode + customer locale. If not, they'll be taken from the system config for the customer's agent and locale. &lt;br /&gt;
&lt;br /&gt;
You can obtain the generated LaTeX code for a particular invoice with ''view/cust_bill-tex.cgi'', which takes all the same parameters as ''view/cust_bill.cgi''. &lt;br /&gt;
&lt;br /&gt;
The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside. &lt;br /&gt;
&lt;br /&gt;
One useful tool for editing a template is Overleaf ([https://www.overleaf.com/ https://www.overleaf.com/]), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of &amp;quot;edit template, set config, download and examine PDF&amp;quot;, especially since it will display LaTeX errors in a semi-useful way. &lt;br /&gt;
&lt;br /&gt;
== Positioning  ==&lt;br /&gt;
This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the &amp;quot;textpos&amp;quot; package. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;textpos&amp;quot; requires you to set up a grid system, like this: &lt;br /&gt;
&lt;br /&gt;
 \usepackage[absolute]{textpos}&lt;br /&gt;
 \setlength{\TPHorizModule}{1in}&lt;br /&gt;
 \setlength{\TPVertModule}{1in}&lt;br /&gt;
&lt;br /&gt;
The environment for a fixed position on the page is &amp;quot;textblock&amp;quot;. The syntax is odd; notice parentheses rather than braces, and numbers without units. &lt;br /&gt;
&lt;br /&gt;
 \begin{textblock}{3.25}(0.625, 0.5)&lt;br /&gt;
   \footnotesize{&lt;br /&gt;
     This text is in a box starting 0.625in left and 0.5in down from the page&lt;br /&gt;
     origin, in a box 3.25in wide. It will fit inside an envelope window.&lt;br /&gt;
   }&lt;br /&gt;
 \end{textblock}&lt;br /&gt;
&lt;br /&gt;
The textblock behaves a lot like a minipage, and can contain almost anything; in particular, \includegraphics works. It's positioned after everything else on the page, which means there's no protection against overprinting anything. &lt;br /&gt;
&lt;br /&gt;
== The standard invoice template  ==&lt;br /&gt;
=== Preamble  ===&lt;br /&gt;
Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document. &lt;br /&gt;
&lt;br /&gt;
* &amp;quot;fancyhdr&amp;quot; supplies the \fancyhead and \fancyfoot commands to place left, right, and center headers and footers. &lt;br /&gt;
* &amp;quot;lastpage&amp;quot; creates the LastPage reference for displaying &amp;quot;page X of Y&amp;quot; labels. &lt;br /&gt;
* &amp;quot;ifthen&amp;quot; provides the \ifthenelse command. &lt;br /&gt;
* &amp;quot;array&amp;quot; provides support for custom table column formats. &lt;br /&gt;
* &amp;quot;longtable&amp;quot; provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the &amp;quot;\LTcouponspace&amp;quot; variable. &lt;br /&gt;
* &amp;quot;afterpage&amp;quot; was removed from the template in May 2005 and we can remove the package at some point. &lt;br /&gt;
* &amp;quot;multirow&amp;quot; is used to make a table cell in the coupon to hold the return address. &lt;br /&gt;
* &amp;quot;bigstrut&amp;quot; appears not to be used. &lt;br /&gt;
* &amp;quot;truncate&amp;quot; provides a command to apply a width limit to a line of text, so that anything past that gets replaced with an ellipsis. This is highly encouraged for things like package descriptions that could otherwise overflow their table cells. &lt;br /&gt;
* &amp;quot;graphicx&amp;quot; provides the \includegraphics command for inserting the EPS of the logo. &lt;br /&gt;
* &amp;quot;inputenc&amp;quot; and &amp;quot;fontenc&amp;quot; make LaTeX play nicely with UTF-8 characters. &lt;br /&gt;
* &amp;quot;background&amp;quot; is used to create watermarks (such as are used for some past-due notices, or voided invoices). The \backgroundsetup command takes a TikZ argument list, which is extremely powerful. See &amp;quot;texdoc pgf&amp;quot; for all the horrifying details. &lt;br /&gt;
&lt;br /&gt;
Useful things to add here: &lt;br /&gt;
&lt;br /&gt;
* &amp;quot;textpos&amp;quot;: see above. &lt;br /&gt;
* &amp;quot;geometry&amp;quot; to manage the margins and headers in a centralized way. It also takes the [showframe] option, which will draw lines to visualize the page layout. &lt;br /&gt;
* &amp;quot;xcolor&amp;quot; if you want color. &lt;br /&gt;
* &amp;quot;tabularx&amp;quot; and/or &amp;quot;tabulary&amp;quot; add more options for setting table column widths. &lt;br /&gt;
&lt;br /&gt;
 \documentclass[letterpaper]{article}&lt;br /&gt;
&lt;br /&gt;
 \usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}&lt;br /&gt;
 \usepackage[breakwords]{truncate}&amp;amp;nbsp;% to avoid overflowing boxes&lt;br /&gt;
 \usepackage{graphicx}    &amp;amp;nbsp;% required for logo graphic&lt;br /&gt;
 \usepackage[utf8]{inputenc}            &amp;amp;nbsp;% multilanguage support&lt;br /&gt;
 \usepackage[T1]{fontenc}&lt;br /&gt;
 [@-- if ( length($watermark) ) {&lt;br /&gt;
   $OUT .= '&lt;br /&gt;
 \usepackage{background}&lt;br /&gt;
 \backgroundsetup{&lt;br /&gt;
   placement=center,&lt;br /&gt;
   opacity=0.25,&lt;br /&gt;
   color=black,&lt;br /&gt;
   angle=0,&lt;br /&gt;
   contents=' . $watermark . '&lt;br /&gt;
 }';&lt;br /&gt;
 }&lt;br /&gt;
 ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Set up dimensions. Most of these were determined empirically and really ought to use the &amp;quot;geometry&amp;quot; package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight. &lt;br /&gt;
&lt;br /&gt;
See [https://en.wikibooks.org/wiki/LaTeX/Page_Layout LaTeX Page Layout] and [http://mirrors.ctan.org/macros/latex/base/letter.pdf texdoc letter] for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that. &lt;br /&gt;
&lt;br /&gt;
If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container. &lt;br /&gt;
&lt;br /&gt;
Commands for manipulating length variables: \newlength declares them (and all of these are predeclared in the document stylesheet); \setlength sets them to a value; \addtolength adds to them. &lt;br /&gt;
&lt;br /&gt;
If you're adjusting margins, it can be helpful to add &amp;quot;\usepackage{layout}&amp;quot; to the preamble, and then &amp;quot;\newpage\layout&amp;quot; before the final &amp;quot;\end{document}&amp;quot;. This will print a diagram showing the layout boxes and their dimensions. &lt;br /&gt;
&lt;br /&gt;
\LTchunksize is an internal variable in longtable for how many rows to process at a time. There's probably no reason to change it. &lt;br /&gt;
&lt;br /&gt;
 \addtolength{\voffset}{-0.0cm}   &amp;amp;nbsp;% top margin to top of header&lt;br /&gt;
 \addtolength{\hoffset}{-0.6cm}   &amp;amp;nbsp;% left margin on page&lt;br /&gt;
 \addtolength{\topmargin}{[@-- defined($topmargin)&amp;amp;nbsp;? $topmargin&amp;amp;nbsp;: '-1.00cm' --@]}&lt;br /&gt;
 \setlength{\headheight}{2.0cm}   &amp;amp;nbsp;% height of header&lt;br /&gt;
 \setlength{\headsep}{[@-- defined($headsep)&amp;amp;nbsp;? $headsep&amp;amp;nbsp;: '1.0cm' --@]}&lt;br /&gt;
 \setlength{\footskip}{1.0cm}   &amp;amp;nbsp;% bottom of footer from bottom of text&lt;br /&gt;
&lt;br /&gt;
 %\addtolength{\textwidth}{2.1in}     &amp;amp;nbsp;% width of text&lt;br /&gt;
 \setlength{\textwidth}{19.5cm}&lt;br /&gt;
 \setlength{\textheight}{[@-- defined($textheight)&amp;amp;nbsp;? $textheight&amp;amp;nbsp;: '19.5cm' --@]}&lt;br /&gt;
 \setlength{\oddsidemargin}{-0.9cm} &amp;amp;nbsp;% odd page left margin&lt;br /&gt;
 \setlength{\evensidemargin}{-0.9cm}  &amp;amp;nbsp;% even page left margin&lt;br /&gt;
&lt;br /&gt;
 \LTchunksize=40&lt;br /&gt;
&lt;br /&gt;
$coupon is a local variable containing the entire generated content of the [http://www.freeside.biz/mediawiki/index.php?title=Invoice_coupon_template&amp;amp;action=edit&amp;amp;redlink=1 invoice payment coupon]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string. &lt;br /&gt;
&lt;br /&gt;
\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position). &lt;br /&gt;
&lt;br /&gt;
\extracouponspace is a config setting for how much space to reserve for the coupon. We will &amp;quot;shorten&amp;quot; the first page's \textheight by that much, which moves the starting position of the header up. &lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\headrulewidth}{0pt}&lt;br /&gt;
 \renewcommand{\footrulewidth}{1pt}&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\footrule}{&lt;br /&gt;
 [@--&lt;br /&gt;
   $coupon&amp;amp;nbsp;? '\ifthenelse{\equal{\thepage}{1}}'&amp;amp;nbsp;: ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
 --@]&lt;br /&gt;
   {&lt;br /&gt;
   }&lt;br /&gt;
   {&lt;br /&gt;
     \vbox to 0pt{\rule{\headwidth}{\footrulewidth}\vss}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
 \newcommand{\extracouponspace}{[@-- defined($extracouponspace)&amp;amp;nbsp;? $extracouponspace&amp;amp;nbsp;: '2.7in' --@]}&lt;br /&gt;
&lt;br /&gt;
Positioning mailing addresses. This is a bad way to do it and I'd recommend using the &amp;quot;textpos&amp;quot; package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout. &lt;br /&gt;
&lt;br /&gt;
 % Adjust the inset of the mailing address&lt;br /&gt;
 \newcommand{\addressinset}[1][]{\hspace{1.0cm}}&lt;br /&gt;
&lt;br /&gt;
 % Adjust the inset of the return address and logo&lt;br /&gt;
 \newcommand{\returninset}[1][]{\hspace{-0.25cm}}&lt;br /&gt;
&lt;br /&gt;
 % New command for address lines i.e. skip them if blank&lt;br /&gt;
 \newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}&lt;br /&gt;
&lt;br /&gt;
 % Inserts dollar symbol&lt;br /&gt;
 \newcommand{\dollar}[1][]{\symbol{36}}&lt;br /&gt;
&lt;br /&gt;
=== Headers/footers  ===&lt;br /&gt;
&amp;quot;fancyhdr&amp;quot; allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer. &lt;br /&gt;
&lt;br /&gt;
These commands are supposed to remove the default &amp;quot;plain&amp;quot; style page footer, but we don't use that page style so it's probably not needed. &lt;br /&gt;
&lt;br /&gt;
 % Remove plain style header/footer&lt;br /&gt;
 \fancypagestyle{plain}{&lt;br /&gt;
   \fancyhead{}&lt;br /&gt;
 }&lt;br /&gt;
 \fancyhf{}&lt;br /&gt;
&lt;br /&gt;
The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position. &lt;br /&gt;
&lt;br /&gt;
The footer to display below the coupon (or if there isn't one) is configurable as '''invoice_latexfooter'''. The footer to show on subsequent pages is '''invoice_latexsmallfooter'''. By default they're both just the company name. &lt;br /&gt;
&lt;br /&gt;
 % Define fancy header/footer for first and subsequent pages&lt;br /&gt;
 \fancyfoot[C]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   {&amp;amp;nbsp;% First page&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($coupon) {&lt;br /&gt;
     $OUT .= '\vspace{-\extracouponspace}';&lt;br /&gt;
     $OUT .= '\rule[0.5em]{\textwidth}{\footrulewidth}\\\\';&lt;br /&gt;
     $OUT .= $coupon;&lt;br /&gt;
     $OUT .= '\vspace{'.&lt;br /&gt;
       (defined($couponfootsep)&amp;amp;nbsp;? $couponfootsep&amp;amp;nbsp;: '0.2in') .&lt;br /&gt;
       '}';&lt;br /&gt;
   }&lt;br /&gt;
   ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
 --@] [@-- $smallerfooter&amp;amp;nbsp;? '\scriptsize{'&amp;amp;nbsp;: '\small{' --@]&lt;br /&gt;
 [@-- $footer --@]&lt;br /&gt;
     }[@-- $coupon&amp;amp;nbsp;? '\vspace{\extracouponspace}'&amp;amp;nbsp;:  ''--@]''&lt;br /&gt;
   }&lt;br /&gt;
   {&amp;amp;nbsp;% ... pages&lt;br /&gt;
     [@-- $smallerfooter&amp;amp;nbsp;? '\scriptsize{'&amp;amp;nbsp;: '\small{' --@]&lt;br /&gt;
 [@-- $smallfooter --@]&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side footer, showing the page number. &amp;quot;emt()&amp;quot; localizes text to the customer's language and escapes for LaTeX. &amp;quot;~&amp;quot; is a non-breaking space character. &lt;br /&gt;
&lt;br /&gt;
 \fancyfoot[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   {&amp;amp;nbsp;% First page&lt;br /&gt;
   }&lt;br /&gt;
   {&amp;amp;nbsp;% ... pages&lt;br /&gt;
     \small{\thepage~[@-- emt('of') --@]~\pageref{LastPage}}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The left side header, containing the return address and logo on the first page (and nothing on later pages). \returninset is just a horizontal adjustment. \makebox{} tells LaTeX to put its contents into a single box. That's probably unnecessary because the only thing in the box is a table, which is already a single box. &lt;br /&gt;
&lt;br /&gt;
\begin{tabular}{ll} creates a two-column table. In the first column is a &amp;quot;minipage&amp;quot;, which is just a fixed-width container environment (5.5cm here) with its bottom edge aligned to the table cell it's in. In here we put the evaluated &amp;quot;returnaddress&amp;quot; template. &lt;br /&gt;
&lt;br /&gt;
Tables in LaTeX always use &amp;amp; to separate columns and \\ to separate rows. A row must always contain as many columns as in the column spec; otherwise LaTeX will guess about what you mean and will usually get it wrong. \\ at the end of the last row is optional. &lt;br /&gt;
&lt;br /&gt;
The second column contains the logo graphic. See &amp;quot;texdoc graphicx&amp;quot; for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: &amp;quot;\includegraphics[height=0.5in]{[@-- $logo_file --@]}&amp;quot;. &lt;br /&gt;
&lt;br /&gt;
 \fancyhead[L]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   {&amp;amp;nbsp;% First page&lt;br /&gt;
     \returninset&lt;br /&gt;
     \makebox{&lt;br /&gt;
       \begin{tabular}{ll}&lt;br /&gt;
         \begin{minipage}[b]{5.5cm}&lt;br /&gt;
 [@-- $returnaddress --@]&lt;br /&gt;
         \end{minipage} &amp;amp;&lt;br /&gt;
         \includegraphics{[@-- $logo_file --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
   {&amp;amp;nbsp;% ... pages&lt;br /&gt;
    &amp;amp;nbsp;%\includegraphics{[@-- $logo_file --@]}&amp;amp;nbsp;% Uncomment if you want the logo on all pages.&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side header, containing the invoice date, invnum, and custnum in a three-column table with each column centered ({ccc}). &amp;quot;$no_date&amp;quot; and &amp;quot;$no_number&amp;quot; are both enabled by arguments to print_generic() and used when printing statements. &lt;br /&gt;
&lt;br /&gt;
Note that &amp;quot;$date&amp;quot; is already formatted using time2str_local('long'), so something like &amp;quot;Feb 1st, 2017&amp;quot;. The template can call time2str() to format other dates; this is an alias to time2str_local so it will use the customer's language settings and LaTeX-escape the result. &lt;br /&gt;
&lt;br /&gt;
This is followed by a horizontal line spanning the table (\hline), then a row where the middle cell contains the $notice_name (usually &amp;quot;Invoice&amp;quot;) in \huge font, in small caps (\textsc). The \rule{0pt}{5ex} creates a vertical strut (a zero-width rule) which increases the row height to 5ex. Then another \hline. &lt;br /&gt;
&lt;br /&gt;
Note that \hline should never be followed by \\ and will cause errors if it is. &lt;br /&gt;
&lt;br /&gt;
On subsequent pages we show a smaller version without the notice name. &lt;br /&gt;
&lt;br /&gt;
 \fancyhead[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   {&amp;amp;nbsp;% First page&lt;br /&gt;
     \begin{tabular}{ccc}&lt;br /&gt;
     [@-- join(' &amp;amp; ', ( $no_date  &amp;amp;nbsp;? &amp;amp;nbsp;'': emt('Invoice date') ),''&lt;br /&gt;
                      ( $no_number&amp;amp;nbsp;? &amp;amp;nbsp;'': emt('Invoice #')    ),''&lt;br /&gt;
                      emt('Customer #')&lt;br /&gt;
              )&lt;br /&gt;
     --@]\\&lt;br /&gt;
     \vspace{0.2cm}&lt;br /&gt;
     \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]} \\\hline&lt;br /&gt;
     \rule{0pt}{5ex} &amp;amp;~~ \huge{\textsc{[@-- emt($notice_name) --@]}} &amp;amp; \\&lt;br /&gt;
     \vspace{-0.2cm}&lt;br /&gt;
      &amp;amp; &amp;amp; \\\hline&lt;br /&gt;
     \end{tabular}&lt;br /&gt;
   }&lt;br /&gt;
   {&amp;amp;nbsp;% ... pages&lt;br /&gt;
     \small{&lt;br /&gt;
       \begin{tabular}{lll}&lt;br /&gt;
       [@-- join(' &amp;amp; ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\&lt;br /&gt;
       \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Enable the fancyhdr page style and set the font family. &lt;br /&gt;
&lt;br /&gt;
 \pagestyle{fancy}&lt;br /&gt;
 %% Font options are:&lt;br /&gt;
 %%  bch Bitsream Charter&lt;br /&gt;
 %%  put Utopia&lt;br /&gt;
 %%  phv Adobe Helvetica&lt;br /&gt;
 %%  pnc New Century Schoolbook&lt;br /&gt;
 %%  ptm Times&lt;br /&gt;
 %%  pcr Courier&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\familydefault}{phv}&lt;br /&gt;
&lt;br /&gt;
=== Line item table macros  ===&lt;br /&gt;
This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are: &lt;br /&gt;
&lt;br /&gt;
* Package number &lt;br /&gt;
* Description (6 columns) &lt;br /&gt;
* Price &lt;br /&gt;
&lt;br /&gt;
(The 6 columns are used separately for showing CDRs, if there are any.) &lt;br /&gt;
&lt;br /&gt;
If '''invoice-unitprice''' is on, it's: &lt;br /&gt;
&lt;br /&gt;
* Package number &lt;br /&gt;
* Description (4 columns) &lt;br /&gt;
* Unit price &lt;br /&gt;
* Quantity &lt;br /&gt;
* Price &lt;br /&gt;
&lt;br /&gt;
According to that setting, create macros for how many columns and how much space to allocate to the description, and for the headers of the Unit Price and Unit Quantity columns. &lt;br /&gt;
&lt;br /&gt;
 % Commands for freeside table header...&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FSdescriptionlength} { [@-- $unitprices&amp;amp;nbsp;? '8.2cm'&amp;amp;nbsp;: '12.8cm' --@] }&lt;br /&gt;
 \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices&amp;amp;nbsp;? '4'&amp;amp;nbsp;: '6' --@] }&lt;br /&gt;
 \newcommand{\FSunitcolumns}{ [@-- &lt;br /&gt;
   $unitprices&lt;br /&gt;
  &amp;amp;nbsp;? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &amp;amp;' .&lt;br /&gt;
     '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} &amp;amp; ' &lt;br /&gt;
  &amp;amp;nbsp;:  ''--@] }''&lt;br /&gt;
&lt;br /&gt;
\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c). &lt;br /&gt;
&lt;br /&gt;
\truncate renders its argument into a space with a maximum width. It will break at word boundaries and insert an ellipsis afterward. Note that if we didn't do this, longtable would either squish the surrounding columns or push them off the page rather than word-wrap the description. &lt;br /&gt;
&lt;br /&gt;
\FSusagehead is the same, but used for usage summary sections that show the number of calls and total minutes. The '''usage_class_summary''' config turns this on. &lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FShead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{\FSdescriptioncolumncount}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \FSunitcolumns&lt;br /&gt;
   \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FSusagehead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Calls') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Duration') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Amount') --@]} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Next, the commands to actually display line items. &lt;br /&gt;
&lt;br /&gt;
\FSdesc will be called once for each element in the @detail_items array. This takes five arguments (that's the [5] in the declaration) which are the values to put in the pkgnum, description, unit price, quantity, and price columns. If unit prices are off then arguments 3 and 4 will be empty. &lt;br /&gt;
&lt;br /&gt;
\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font. &lt;br /&gt;
&lt;br /&gt;
\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() ''into LaTeX table rows'' (pre-escaped, delimited with &amp;amp;). So, the macro just skips the first column and outputs its argument. &lt;br /&gt;
&lt;br /&gt;
\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column. &lt;br /&gt;
&lt;br /&gt;
Finally, \FSusagedesc is for usage summary sections, and puts the total calls, duration, and amount in the correct columns. &lt;br /&gt;
&lt;br /&gt;
 % ...description...&lt;br /&gt;
 \newcommand{\FSdesc}[5]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{[@-- $unitprices&amp;amp;nbsp;? '4'&amp;amp;nbsp;: '6' --@]}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{#2}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
 [@-- $unitprices&amp;amp;nbsp;? '  \multicolumn{1}{r}{\textbf{#3}} &amp;amp;'.&amp;quot;\n&amp;quot;.&lt;br /&gt;
                    '  \multicolumn{1}{r}{\textbf{#4}} &amp;amp;'.&amp;quot;\n&amp;quot;&lt;br /&gt;
                 &amp;amp;nbsp;: &lt;br /&gt;
 --@]&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#5}}\\&lt;br /&gt;
 }&lt;br /&gt;
 % ...extended description...&lt;br /&gt;
 \newcommand{\FSextdesc}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{\small{[http://www.freeside.biz/mediawiki/index.php?title=User:Mark&amp;amp;action=edit&amp;amp;redlink=1 Mark] ([http://www.freeside.biz/mediawiki/index.php?title=User_talk:Mark&amp;amp;action=edit&amp;amp;redlink=1 talk])#1}}&lt;br /&gt;
   } \\&lt;br /&gt;
 }&lt;br /&gt;
 % ...call detail (multiple columns already)...&lt;br /&gt;
 \newcommand{\FScalldetail}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   [http://www.freeside.biz/mediawiki/index.php?title=User:Mark&amp;amp;action=edit&amp;amp;redlink=1 Mark] ([http://www.freeside.biz/mediawiki/index.php?title=User_talk:Mark&amp;amp;action=edit&amp;amp;redlink=1 talk])#1&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
 }&lt;br /&gt;
 % ...and total line items (which use the full 12.8cm length, ignoring&lt;br /&gt;
 % unitprice/quantity&lt;br /&gt;
 \newcommand{\FStotaldesc}[2]{&lt;br /&gt;
   &amp;amp; \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{#1}&lt;br /&gt;
   } &amp;amp; #2\\&lt;br /&gt;
 }&lt;br /&gt;
 % ...usage class summary&lt;br /&gt;
 \newcommand{\FSusagedesc}[4]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#2}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#3}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#4}}&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Main document  ===&lt;br /&gt;
First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line. &lt;br /&gt;
&lt;br /&gt;
 \begin{document}&lt;br /&gt;
 % Headers and footers defined for the first page&lt;br /&gt;
 \addressinset \rule{0.5cm}{0cm} &lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{7.0cm}&lt;br /&gt;
 \vspace{[@-- defined($addresssep)&amp;amp;nbsp;? $addresssep&amp;amp;nbsp;: '0.25cm' --@]}&lt;br /&gt;
 \textbf{[@-- $payname --@]}\\&lt;br /&gt;
 \addressline{[@-- $company --@]}&lt;br /&gt;
 \addressline{[@-- $address1 --@]}&lt;br /&gt;
 \addressline{[@-- $address2 --@]}&lt;br /&gt;
 \addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}&lt;br /&gt;
 \addressline{[@-- $country --@]}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
&lt;br /&gt;
Make another minipage for the service address, terms, and PO number. $ship_enable is true if the global '''invoice-ship_address''' or per-customer &amp;quot;invoice_ship_address&amp;quot; flag is on. &lt;br /&gt;
&lt;br /&gt;
The initial \hfill spreads out the two minipages so that they go all the way to the margins. The 'terms' and 'po_line' should probably be in \addressline{} macros but currently aren't. &lt;br /&gt;
&lt;br /&gt;
After that, skip some vertical space. (If you need more vertical space for an invoice format, the 1.5cm here can be cut.) &lt;br /&gt;
&lt;br /&gt;
 \hfill&lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{6.4cm}&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($ship_enable) {&lt;br /&gt;
     $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_company}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address1}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address2}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_city, $ship_state~~$ship_zip}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_country}&amp;quot;;&lt;br /&gt;
     $OUT .= '~\\\\';&lt;br /&gt;
   }else{&lt;br /&gt;
     $OUT .= ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
 \begin{flushright}&lt;br /&gt;
 [@-- $terms&amp;amp;nbsp;? emt('Terms') . ': ' . emt($terms)&amp;amp;nbsp;:  ''--@]\\''&lt;br /&gt;
 [@-- $po_line --@]\\&lt;br /&gt;
 \end{flushright}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
 \vspace{1.5cm}&lt;br /&gt;
&lt;br /&gt;
Insert the [http://www.freeside.biz/mediawiki/index.php?title=Summary&amp;amp;action=edit&amp;amp;redlink=1 Summary] if there is one. The last thing in the summary template is a page break. &lt;br /&gt;
&lt;br /&gt;
 %&lt;br /&gt;
 [@-- $summary --@]&lt;br /&gt;
 %&lt;br /&gt;
&lt;br /&gt;
=== Sections  ===&lt;br /&gt;
Start creating sections, according to the @sections array. Section entries contain the following keys: &lt;br /&gt;
&lt;br /&gt;
* description &lt;br /&gt;
* category (pkg_category.categoryname, for package sections by category) &lt;br /&gt;
* location (hashref of location fields, for package sections by location) &lt;br /&gt;
* pretotal (formatted with \FStotaldesc, before detail lines) &lt;br /&gt;
* subtotal (formatted with \FStotaldesc, at the end) &lt;br /&gt;
* posttotal (line to display after the end of the section; used for &amp;quot;Balance Due&amp;quot; message and a few other things) &lt;br /&gt;
* sort_weight (sort order, but also does complicated things) &lt;br /&gt;
&lt;br /&gt;
and optionally some flags: &lt;br /&gt;
&lt;br /&gt;
* post_total (put the section at the end of the invoice) &lt;br /&gt;
* summarized (omit the section entirely) &lt;br /&gt;
* usage_section (use \FSusagedesc instead of \FSdesc, see above) &lt;br /&gt;
* no_subtotal (omit the subtotal line) &lt;br /&gt;
&lt;br /&gt;
If '''invoice_sections''' is enabled: the $multisection flag in print_generic() is turned on, and @sections is filled by calling FS::Template_Mixin::_items_sections, which makes a section for each package category. Line items for packages go into the package category's section. '''invoice_sections_method''' can be used to divide sections by location rather than by category. In addition, sections are created for previous invoices, taxes, and &amp;quot;adjustments&amp;quot; (credits and payments to this invoice). &lt;br /&gt;
&lt;br /&gt;
If not: a single section is created, with empty description, containing previous invoices, current charges, and adjustments (after the total). '''previous_balance-section''' will make a previous invoices section anyway. &lt;br /&gt;
&lt;br /&gt;
Additional sections are created in some cases. '''svc_phone_sections''' creates a section for each phone line (see RT#6592; note that this customer is no longer active). '''voip-cust_accountcode_cdr''' creates a section for each accountcode (see RT#12181). '''discount-show_available''' creates a section with information on available discounts. '''usage_class_summary''' creates a section with usage subtotals. &lt;br /&gt;
&lt;br /&gt;
'''finance_pkgclass''' specifies the package class containing &amp;quot;finance charges&amp;quot;, which is handled specially. That class's category name is passed to the template as $finance_section, and the sum of charges in that category is passed as $finance_amount. That amount is broken out from the &amp;quot;Current charges&amp;quot; line in the summary section. On a multisection summary-format invoice, the finance charges will also ''not'' be shown in the body of the invoice. I don't know why. &lt;br /&gt;
&lt;br /&gt;
Loop over all sections (except for the finance charges, maybe). Inside the loop we'll do everything by appending to $OUT. &lt;br /&gt;
&lt;br /&gt;
 \section*{}&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section ( grep {&amp;amp;nbsp;!$summary || $_-&amp;gt;{description} ne $finance_section } @sections ) {&lt;br /&gt;
&lt;br /&gt;
We're going to do many things if &amp;quot;!$summary&amp;quot;. If there's a summary section, the pretotals and posttotals are redundant so skip them. &lt;br /&gt;
&lt;br /&gt;
    if ($section-&amp;gt;{'pretotal'} &amp;amp;&amp;amp;&amp;amp;nbsp;!$summary) {&lt;br /&gt;
      $OUT .= '\begin{flushright}';&lt;br /&gt;
      $OUT .= '\large\textsc{'. $section-&amp;gt;{'pretotal'}. '}\\\\';&lt;br /&gt;
      $OUT .= '\\end{flushright}';&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
Start a new page if this is a &amp;quot;post_total&amp;quot; section. As far as I know these are used only for usage details. I think the 'summarized' flag is no longer used for anything. &lt;br /&gt;
&lt;br /&gt;
The section heading is going to be a caption placed inside the main table. \captionsetup sets properties for the caption (left justified, bold, small caps, Large size). If this is the first page, also tell longtable (via \LTextracouponspace) to shorten the effective length of the page so that it doesn't overprint the coupon. &lt;br /&gt;
&lt;br /&gt;
    $OUT .= '\pagebreak' if $section-&amp;gt;{'post_total'};&lt;br /&gt;
    unless ($section-&amp;gt;{'summarized'} ) {&lt;br /&gt;
      $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';&lt;br /&gt;
      $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'&lt;br /&gt;
        if $coupon;&lt;br /&gt;
&lt;br /&gt;
Start the longtable that will contain the section's line items. Eight columns, pkgnum (centered), description and optional unitprice/quantity (left), then price (right). &lt;br /&gt;
&lt;br /&gt;
\caption* produces a table caption without prefixing it with &amp;quot;Table 1&amp;quot;. The caption is the section description, or 'Charges' if it's null (except that if packages are sectioned by location, we put one together from the location fields). &lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\begin{longtable}{cllllllr}';&lt;br /&gt;
      $OUT .= '\caption*{ ';&lt;br /&gt;
      if ($section-&amp;gt;{'location'}) {&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'label_prefix'}. ': '&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'label_prefix'});&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'address1'};&lt;br /&gt;
        $OUT .= ', ' . $section-&amp;gt;{'location'}{'address2'}&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'address2'});&lt;br /&gt;
        $OUT .= ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'city'} . ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'state'} . '~' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'zip'};&lt;br /&gt;
      } elsif ( $section-&amp;gt;{'description'} ) {&lt;br /&gt;
        $OUT .= ($section-&amp;gt;{'description'});&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= emt('Charges');&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '}\\\\';&lt;br /&gt;
&lt;br /&gt;
The point of a longtable is that it can span multiple pages, including printing head/foot rows at the top/bottom of each page automatically. The protocol for setting these up is to specify the first head, per-page head, per-page foot, and last foot, then the rest of the table contents. Here, we set the first head to be \FShead (or \FSusagehead). \endfirsthead means we're done specifying it. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;header_generator&amp;quot; and other foo_generators are from an old mechanism that injected callbacks into the template data. This is strongly deprecated. &lt;br /&gt;
&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endfirsthead';&lt;br /&gt;
&lt;br /&gt;
The per-page head: a row with a &amp;quot;Continued&amp;quot; message, then \FShead again. The per-page foot is the same. &lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endhead';&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';&lt;br /&gt;
      $OUT .= '\endfoot';&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
&lt;br /&gt;
The final foot for the section. For multisection invoices this contains the section subtotal: &lt;br /&gt;
&lt;br /&gt;
      if (scalar(@sections) &amp;gt; 1 and&amp;amp;nbsp;!$section-&amp;gt;{no_subtotal}) {&lt;br /&gt;
        if ($section-&amp;gt;{total_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{total_generator}}($section);&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FStotaldesc{' . $section-&amp;gt;{'description'} . ' Total}' .&lt;br /&gt;
                  '{' . $section-&amp;gt;{'subtotal'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
Single-section invoices use &amp;quot;@total_items&amp;quot; for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot. &lt;br /&gt;
&lt;br /&gt;
      &amp;lt;nowiki&amp;gt;#if ($section == $sections[$#sections]) {&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
        foreach my $line (grep {$_-&amp;gt;{section}-&amp;gt;{description} eq $section-&amp;gt;{description}} @total_items) {&lt;br /&gt;
          if ($section-&amp;gt;{total_line_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{total_line_generator}}($line);&lt;br /&gt;
          } else {&lt;br /&gt;
            $OUT .= '\FStotaldesc{' . $line-&amp;gt;{'total_item'} . '}' .&lt;br /&gt;
                    '{' . $line-&amp;gt;{'total_amount'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      &amp;lt;nowiki&amp;gt;#}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
      $OUT .= '\endlastfoot';&lt;br /&gt;
&lt;br /&gt;
Line items are passed in @detail_items. A line item entry can contain: &lt;br /&gt;
&lt;br /&gt;
* section (reference) &lt;br /&gt;
* ref (pkgnum) &lt;br /&gt;
* description &lt;br /&gt;
* ext_description (arrayref of additional lines) &lt;br /&gt;
* quantity &lt;br /&gt;
* amount &lt;br /&gt;
* duration (for usage summary sections) &lt;br /&gt;
* pkgpart &lt;br /&gt;
* unit_amount (unit price) &lt;br /&gt;
* locationnum &lt;br /&gt;
* svc_label (the primary service's label) &lt;br /&gt;
&lt;br /&gt;
Line items are created in several places in Template_Mixin and TemplateItem_Mixin, most importantly _items_cust_bill_pkg. &lt;br /&gt;
&lt;br /&gt;
@detail_items is a single array; we find the lines belonging to this section by checking their 'section' element. If there's only one section, process all the lines. &lt;br /&gt;
&lt;br /&gt;
      my $lastref = 0;&lt;br /&gt;
      foreach my $line (&lt;br /&gt;
        grep { ( scalar( @sections ) &amp;gt; 1 &lt;br /&gt;
              &amp;amp;nbsp;? $section-&amp;gt;{'description'} eq $_-&amp;gt;{'section'}-&amp;gt;{'description'}&lt;br /&gt;
              &amp;amp;nbsp;: 1&lt;br /&gt;
             ) }&lt;br /&gt;
        @detail_items )&lt;br /&gt;
      {&lt;br /&gt;
        my $ext_description = $line-&amp;gt;{'ext_description'};&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;nowiki&amp;gt;# Don't break-up small packages.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
        my $rowbreak = @$ext_description &amp;lt; 5&amp;amp;nbsp;? '*'&amp;amp;nbsp;: ''&amp;lt;nowiki&amp;gt;;&amp;lt;/nowiki&amp;gt;''&lt;br /&gt;
&lt;br /&gt;
$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum. &lt;br /&gt;
&lt;br /&gt;
If this is a usage summary section, convert the duration to minutes/seconds and call \FSusagedesc, passing the description, quantity (number of calls), duration, and total price. &lt;br /&gt;
&lt;br /&gt;
If it's a normal section, call \FSdesc, passing the description, unit price, quantity, and amount. If unit prices aren't in use, or there isn't one on this line item, then leave unit price and quantity blank. &lt;br /&gt;
&lt;br /&gt;
&amp;quot;$rowbreak&amp;quot; is a flag for whether to allow a page break. In general we want to keep line items and their ext_description lines together, unless there are a lot of description lines. So if there are fewer than 5 of them, we set $rowbreak = '*', which after \FSdesc{} is evaluated, results in the table row delimiter being &amp;quot;\\*&amp;quot;. This tells LaTeX to avoid putting a page break immediately after this row. Probably we should set $rowbreak = ''on the last description line to explicitly allow page breaks between packages.'' &lt;br /&gt;
&lt;br /&gt;
        $OUT .= &amp;quot;\\hline\n&amp;quot; if (($line-&amp;gt;{'ref'} || 0) ne $lastref);&lt;br /&gt;
        if ($section-&amp;gt;{description_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{description_generator}}($line);&lt;br /&gt;
        } elsif ($section-&amp;gt;{usage_section}) {&lt;br /&gt;
          my $minutes = sprintf('%d', $line-&amp;gt;{'duration'} / 60);&lt;br /&gt;
          my $seconds = $line-&amp;gt;{'duration'}&amp;amp;nbsp;% 60;&lt;br /&gt;
          $OUT .= '\FSusagedesc&lt;br /&gt;
            {' . $line-&amp;gt;{'description'} . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'quantity'} . '}&lt;br /&gt;
            {' . $minutes . 'm ' . $seconds . 's' . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'amount'} . '}';&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FSdesc'.&lt;br /&gt;
                  '{}'.&lt;br /&gt;
                  '{' . $line-&amp;gt;{'description'} . '}'&amp;amp;nbsp;;&lt;br /&gt;
          if ( $unitprices and length($line-&amp;gt;{'unit_amount'}) ) {&lt;br /&gt;
            &amp;lt;nowiki&amp;gt;# then show the unit amount and quantity&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
            $OUT .= &lt;br /&gt;
                '{\\dollar' . $line-&amp;gt;{'unit_amount'} . '}'.&lt;br /&gt;
                '{'         . $line-&amp;gt;{'quantity'}    . '}';&lt;br /&gt;
          } else {&lt;br /&gt;
            &amp;lt;nowiki&amp;gt;# leave those columns blank&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
            $OUT .= '{}{}';&lt;br /&gt;
          }&lt;br /&gt;
          $OUT .= '{\\dollar' . $line-&amp;gt;{'amount'} . &amp;quot;}${rowbreak}\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        $lastref = $line-&amp;gt;{'ref'} || 0;&lt;br /&gt;
&lt;br /&gt;
Output ext_description lines. We assume that any of them that contain an unescaped &amp;quot;&amp;amp;&amp;quot; are call details and should use \FScalldetail. Otherwise they use \FSextdesc. &lt;br /&gt;
&lt;br /&gt;
        foreach my $ext_desc (@$ext_description) {&lt;br /&gt;
          if ($section-&amp;gt;{extended_description_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{extended_description_generator}}($ext_desc);&lt;br /&gt;
          } elsif ( $ext_desc&amp;amp;nbsp;!~ /[^\\]&amp;amp;/ ) {&lt;br /&gt;
            $OUT .= '\FSextdesc{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          } else { # call detail&lt;br /&gt;
            $OUT .= '\FScalldetail{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
End the longtable. If there is a posttotal item in this section (the final Balance Due / Credit Balance message, previous invoice aging, a few other things) then show it right-justified, in the same formatting used for the caption. &lt;br /&gt;
&lt;br /&gt;
       $OUT .= '\end{longtable}';&lt;br /&gt;
     }&lt;br /&gt;
     if ($section-&amp;gt;{'posttotal'}) {&lt;br /&gt;
       $OUT .= '\begin{flushright}';&lt;br /&gt;
       $OUT .= '\normalfont\large\bfseries\textsc{'. $section-&amp;gt;{'posttotal'}. '}\\\\';&lt;br /&gt;
       $OUT .= '\\end{flushright}';&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
=== Notes  ===&lt;br /&gt;
For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.) &lt;br /&gt;
&lt;br /&gt;
There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space. &lt;br /&gt;
&lt;br /&gt;
However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the ''strut'' is at the bottom of the page, keeping the coupon space clear. &lt;br /&gt;
&lt;br /&gt;
 --@]&lt;br /&gt;
 \vfill&lt;br /&gt;
 \begin{minipage}[t]{\textwidth}&lt;br /&gt;
   [@-- length($summary)&lt;br /&gt;
         &amp;amp;nbsp;? &lt;br /&gt;
        &amp;amp;nbsp;: ( $smallernotes&lt;br /&gt;
              &amp;amp;nbsp;? '\scriptsize{ '.$notes.' }'&lt;br /&gt;
              &amp;amp;nbsp;: $notes&lt;br /&gt;
           )&lt;br /&gt;
   --@]&lt;br /&gt;
   [@-- $coupon&amp;amp;nbsp;? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}'&amp;amp;nbsp;:  ''--@]''&lt;br /&gt;
 \end{minipage}&lt;br /&gt;
 \end{document}&lt;br /&gt;
&lt;br /&gt;
That's the end of the invoice. &lt;br /&gt;
&lt;br /&gt;
== The summary template  ==&lt;br /&gt;
Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes. &lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{ll}&lt;br /&gt;
 \begin{minipage}{6.4cm}&lt;br /&gt;
 \begin{tabular}{m{0cm}m{6.4cm}}&lt;br /&gt;
 \rule{0cm}{10cm}&amp;amp;\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} &amp;amp;&lt;br /&gt;
&lt;br /&gt;
On the right side, place a 2cm horizontal strut followed by a 12.8cm minipage. This will contain the billing summary. &lt;br /&gt;
&lt;br /&gt;
 \rule{2cm}{0cm}&lt;br /&gt;
 \begin{minipage}{12.8cm}&lt;br /&gt;
&lt;br /&gt;
The billing summary itself is a two-column table, one for labels and one for amounts. The section headings are just labels with no amounts, with lines underneath. &lt;br /&gt;
&lt;br /&gt;
'true_previous_balance' is, as far as we can determine it, the balance that was printed on the customer's previous invoice. It's the sum of all the customer's transactions (invoices, credits, payments, refunds) dated before that invoice, plus the invoice amount. &lt;br /&gt;
&lt;br /&gt;
'balance_adjustments' is the sum of payments and credits received after the previous invoice, but applied to invoices before this current invoice. 'true_previous_balance' - 'balance_adjustments' is the total amount owed on all past invoices. &lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{lr}&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of Previous Balance and Payments}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{Previous Balance}&amp;amp;\textbf{\dollar[@-- $true_previous_balance --@]}\\&lt;br /&gt;
 \textbf{Payments}&amp;amp;\textbf{\dollar[@-- $balance_adjustments --@]}\\&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Balance Outstanding}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of New Charges}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
&lt;br /&gt;
@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. This is followed with \cline{2-2}, which puts a horizontal line under the second column only, and then a row for the total of new charges. &lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section (@summary_subtotals) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'}&amp;amp;nbsp;? $section-&amp;gt;{'description'}&amp;amp;nbsp;: 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
   $OUT .= '\cline{2-2}';&lt;br /&gt;
 --@]&lt;br /&gt;
 \textbf{New Charges Total}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Invoice Summary}} &amp;amp; \\&lt;br /&gt;
 &amp;amp; \\&lt;br /&gt;
 \textbf{Previous Past Due Charges}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\&lt;br /&gt;
 \textbf{Finance charges on overdue amount}&amp;amp;\textbf{\dollar[@-- $finance_amount --@]}\\&lt;br /&gt;
 \textbf{New Charges}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
&lt;br /&gt;
Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the ''current'' invoice. &lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   &amp;lt;nowiki&amp;gt;#false laziness w/invoice_htmlsummary and above&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   foreach my $section ( grep $_-&amp;gt;{adjust_section}, @sections ) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'}&amp;amp;nbsp;? $section-&amp;gt;{'description'}&amp;amp;nbsp;: 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Finally, the customer's balance including the current invoice, and then close the table and minipage, and force a page break so the line items can start on a new page. &lt;br /&gt;
&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Total Amount Due}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $balance) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} \\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \newpage&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Invoice modes  ==&lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php?title=Special:UserLogin&amp;amp;returnto=Invoice+template+documentation Log in] &lt;br /&gt;
&lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Invoice_template_documentation Page] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php?title=Talk:Invoice_template_documentation&amp;amp;action=edit&amp;amp;redlink=1 Discussion] &lt;br /&gt;
&lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Invoice_template_documentation Read] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;amp;action=edit View source] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;amp;action=history View history] &lt;br /&gt;
&lt;br /&gt;
 ===== Navigation =====&lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Main_Page Main page] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Special:RecentChanges Recent changes] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Special:Random Random page] &lt;br /&gt;
&lt;br /&gt;
===== Tools =====&lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Special:WhatLinksHere/Invoice_template_documentation What links here] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Special:RecentChangesLinked/Invoice_template_documentation Related changes] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Special:SpecialPages Special pages] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;amp;printable=yes Printable version] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;amp;oldid=9615 Permanent link] &lt;br /&gt;
&lt;br /&gt;
* This page was last modified on 10 January 2017, at 14:06. &lt;br /&gt;
* This page has been accessed 5 times. &lt;br /&gt;
&lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Freeside:Privacy_policy Privacy policy] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Freeside:About About Freeside] &lt;br /&gt;
* [http://www.freeside.biz/mediawiki/index.php/Freeside:General_disclaimer Disclaimers] &lt;br /&gt;
&lt;br /&gt;
* [[Image:]]&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9615</id>
		<title>Invoice template documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9615"/>
				<updated>2017-01-10T22:06:46Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= General notes =&lt;br /&gt;
&lt;br /&gt;
Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template.&lt;br /&gt;
&lt;br /&gt;
The main function for generating the LaTeX code is FS::Template_Mixin::print_generic. This is a method of the cust_bill object (and also of quotation objects), and takes several arguments described in the perldoc. Most of what this function does is set up the &amp;quot;%invoice_data&amp;quot; hash, which gets imported into local variable space when evaluating the template.&lt;br /&gt;
&lt;br /&gt;
print_generic() actually constructs several templates: 'notes', 'coupon', 'footer', 'smallfooter', 'watermark', and sometimes 'summary'. Exactly which config variable is loaded for each of these is decided in Template_Mixin; look for the line that starts &amp;quot;my $tc&amp;quot;. It's '''invoice_$format$part''' (where $format is either &amp;quot;latex&amp;quot; or &amp;quot;html&amp;quot; and $part is &amp;quot;notes&amp;quot;, &amp;quot;coupon&amp;quot;, etc.). Each template is evaluated, with %invoice_data as arguments, then put back into %invoice_data as &amp;quot;$notes&amp;quot;, &amp;quot;$coupon&amp;quot;, etc. The main template contains code fragments to include them.&lt;br /&gt;
&lt;br /&gt;
To further complicate things, all of these elements have locale overrides AND invoice mode overrides. If print_generic() was called with a 'mode' argument, those templates will be fetched from the invoice mode + customer locale. If not, they'll be taken from the system config for the customer's agent and locale.&lt;br /&gt;
&lt;br /&gt;
You can obtain the generated LaTeX code for a particular invoice with ''view/cust_bill-tex.cgi'', which takes all the same parameters as ''view/cust_bill.cgi''.&lt;br /&gt;
&lt;br /&gt;
The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside.&lt;br /&gt;
&lt;br /&gt;
One useful tool for editing a template is Overleaf (https://www.overleaf.com/), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of &amp;quot;edit template, set config, download and examine PDF&amp;quot;, especially since it will display LaTeX errors in a semi-useful way.&lt;br /&gt;
&lt;br /&gt;
== Positioning ==&lt;br /&gt;
&lt;br /&gt;
This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the &amp;quot;textpos&amp;quot; package.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;textpos&amp;quot; requires you to set up a grid system, like this:&lt;br /&gt;
&lt;br /&gt;
 \usepackage[absolute]{textpos}&lt;br /&gt;
 \setlength{\TPHorizModule}{1in}&lt;br /&gt;
 \setlength{\TPVertModule}{1in}&lt;br /&gt;
&lt;br /&gt;
The environment for a fixed position on the page is &amp;quot;textblock&amp;quot;. The syntax is odd; notice parentheses rather than braces, and numbers without units.&lt;br /&gt;
&lt;br /&gt;
 \begin{textblock}{3.25}(0.625, 0.5)&lt;br /&gt;
   \footnotesize{&lt;br /&gt;
     This text is in a box starting 0.625in left and 0.5in down from the page&lt;br /&gt;
     origin, in a box 3.25in wide. It will fit inside an envelope window.&lt;br /&gt;
   }&lt;br /&gt;
 \end{textblock}&lt;br /&gt;
&lt;br /&gt;
The textblock behaves a lot like a minipage, and can contain almost anything; in particular, \includegraphics works. It's positioned after everything else on the page, which means there's no protection against overprinting anything.&lt;br /&gt;
&lt;br /&gt;
== The standard invoice template ==&lt;br /&gt;
&lt;br /&gt;
=== Preamble ===&lt;br /&gt;
&lt;br /&gt;
Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document.&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;fancyhdr&amp;quot; supplies the \fancyhead and \fancyfoot commands to place left, right, and center headers and footers.&lt;br /&gt;
* &amp;quot;lastpage&amp;quot; creates the LastPage reference for displaying &amp;quot;page X of Y&amp;quot; labels.&lt;br /&gt;
* &amp;quot;ifthen&amp;quot; provides the \ifthenelse command.&lt;br /&gt;
* &amp;quot;array&amp;quot; provides support for custom table column formats.&lt;br /&gt;
* &amp;quot;longtable&amp;quot; provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the &amp;quot;\LTcouponspace&amp;quot; variable.&lt;br /&gt;
* &amp;quot;afterpage&amp;quot; was removed from the template in May 2005 and we can remove the package at some point.&lt;br /&gt;
* &amp;quot;multirow&amp;quot; is used to make a table cell in the coupon to hold the return address.&lt;br /&gt;
* &amp;quot;bigstrut&amp;quot; appears not to be used.&lt;br /&gt;
* &amp;quot;truncate&amp;quot; provides a command to apply a width limit to a line of text, so that anything past that gets replaced with an ellipsis. This is highly encouraged for things like package descriptions that could otherwise overflow their table cells.&lt;br /&gt;
* &amp;quot;graphicx&amp;quot; provides the \includegraphics command for inserting the EPS of the logo.&lt;br /&gt;
* &amp;quot;inputenc&amp;quot; and &amp;quot;fontenc&amp;quot; make LaTeX play nicely with UTF-8 characters.&lt;br /&gt;
* &amp;quot;background&amp;quot; is used to create watermarks (such as are used for some past-due notices, or voided invoices). The \backgroundsetup command takes a TikZ argument list, which is extremely powerful. See &amp;quot;texdoc pgf&amp;quot; for all the horrifying details.&lt;br /&gt;
&lt;br /&gt;
Useful things to add here:&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;textpos&amp;quot;: see above.&lt;br /&gt;
* &amp;quot;geometry&amp;quot; to manage the margins and headers in a centralized way. It also takes the [showframe] option, which will draw lines to visualize the page layout.&lt;br /&gt;
* &amp;quot;xcolor&amp;quot; if you want color.&lt;br /&gt;
* &amp;quot;tabularx&amp;quot; and/or &amp;quot;tabulary&amp;quot; add more options for setting table column widths.&lt;br /&gt;
&lt;br /&gt;
 \documentclass[letterpaper]{article}&lt;br /&gt;
 &lt;br /&gt;
 \usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}&lt;br /&gt;
 \usepackage[breakwords]{truncate} % to avoid overflowing boxes&lt;br /&gt;
 \usepackage{graphicx}     % required for logo graphic&lt;br /&gt;
 \usepackage[utf8]{inputenc}             % multilanguage support&lt;br /&gt;
 \usepackage[T1]{fontenc}&lt;br /&gt;
 [@-- if ( length($watermark) ) {&lt;br /&gt;
   $OUT .= '&lt;br /&gt;
 \usepackage{background}&lt;br /&gt;
 \backgroundsetup{&lt;br /&gt;
   placement=center,&lt;br /&gt;
   opacity=0.25,&lt;br /&gt;
   color=black,&lt;br /&gt;
   angle=0,&lt;br /&gt;
   contents=' . $watermark . '&lt;br /&gt;
 }';&lt;br /&gt;
 }&lt;br /&gt;
 '';&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Set up dimensions. Most of these were determined empirically and really ought to use the &amp;quot;geometry&amp;quot; package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight.&lt;br /&gt;
&lt;br /&gt;
See [https://en.wikibooks.org/wiki/LaTeX/Page_Layout LaTeX Page Layout] and [http://mirrors.ctan.org/macros/latex/base/letter.pdf texdoc letter] for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that.&lt;br /&gt;
&lt;br /&gt;
If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container.&lt;br /&gt;
&lt;br /&gt;
Commands for manipulating length variables: \newlength declares them (and all of these are predeclared in the document stylesheet); \setlength sets them to a value; \addtolength adds to them.&lt;br /&gt;
&lt;br /&gt;
If you're adjusting margins, it can be helpful to add &amp;quot;\usepackage{layout}&amp;quot; to the preamble, and then &amp;quot;\newpage\layout&amp;quot; before the final &amp;quot;\end{document}&amp;quot;. This will print a diagram showing the layout boxes and their dimensions.&lt;br /&gt;
&lt;br /&gt;
\LTchunksize is an internal variable in longtable for how many rows to process at a time. There's probably no reason to change it.&lt;br /&gt;
&lt;br /&gt;
 \addtolength{\voffset}{-0.0cm}    % top margin to top of header&lt;br /&gt;
 \addtolength{\hoffset}{-0.6cm}    % left margin on page&lt;br /&gt;
 \addtolength{\topmargin}{[@-- defined($topmargin) ? $topmargin : '-1.00cm' --@]}&lt;br /&gt;
 \setlength{\headheight}{2.0cm}    % height of header&lt;br /&gt;
 \setlength{\headsep}{[@-- defined($headsep) ? $headsep : '1.0cm' --@]}&lt;br /&gt;
 \setlength{\footskip}{1.0cm}    % bottom of footer from bottom of text&lt;br /&gt;
 &lt;br /&gt;
 %\addtolength{\textwidth}{2.1in}      % width of text&lt;br /&gt;
 \setlength{\textwidth}{19.5cm}&lt;br /&gt;
 \setlength{\textheight}{[@-- defined($textheight) ? $textheight : '19.5cm' --@]}&lt;br /&gt;
 \setlength{\oddsidemargin}{-0.9cm}  % odd page left margin&lt;br /&gt;
 \setlength{\evensidemargin}{-0.9cm}   % even page left margin&lt;br /&gt;
 &lt;br /&gt;
 \LTchunksize=40&lt;br /&gt;
&lt;br /&gt;
$coupon is a local variable containing the entire generated content of the [[Invoice_coupon_template|invoice payment coupon]]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string.&lt;br /&gt;
&lt;br /&gt;
\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position).&lt;br /&gt;
&lt;br /&gt;
\extracouponspace is a config setting for how much space to reserve for the coupon. We will &amp;quot;shorten&amp;quot; the first page's \textheight by that much, which moves the starting position of the header up.&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\headrulewidth}{0pt}&lt;br /&gt;
 \renewcommand{\footrulewidth}{1pt}&lt;br /&gt;
 &lt;br /&gt;
 \renewcommand{\footrule}{&lt;br /&gt;
 [@--&lt;br /&gt;
   $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : '';&lt;br /&gt;
 --@]&lt;br /&gt;
   {&lt;br /&gt;
   }&lt;br /&gt;
   {&lt;br /&gt;
     \vbox to 0pt{\rule{\headwidth}{\footrulewidth}\vss}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\extracouponspace}{[@-- defined($extracouponspace) ? $extracouponspace : '2.7in' --@]}&lt;br /&gt;
&lt;br /&gt;
Positioning mailing addresses. This is a bad way to do it and I'd recommend using the &amp;quot;textpos&amp;quot; package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout.&lt;br /&gt;
 &lt;br /&gt;
 % Adjust the inset of the mailing address&lt;br /&gt;
 \newcommand{\addressinset}[1][]{\hspace{1.0cm}}&lt;br /&gt;
 &lt;br /&gt;
 % Adjust the inset of the return address and logo&lt;br /&gt;
 \newcommand{\returninset}[1][]{\hspace{-0.25cm}}&lt;br /&gt;
 &lt;br /&gt;
 % New command for address lines i.e. skip them if blank&lt;br /&gt;
 \newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}&lt;br /&gt;
 &lt;br /&gt;
 % Inserts dollar symbol&lt;br /&gt;
 \newcommand{\dollar}[1][]{\symbol{36}}&lt;br /&gt;
&lt;br /&gt;
=== Headers/footers ===&lt;br /&gt;
&lt;br /&gt;
&amp;quot;fancyhdr&amp;quot; allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer.&lt;br /&gt;
&lt;br /&gt;
These commands are supposed to remove the default &amp;quot;plain&amp;quot; style page footer, but we don't use that page style so it's probably not needed.&lt;br /&gt;
&lt;br /&gt;
 % Remove plain style header/footer&lt;br /&gt;
 \fancypagestyle{plain}{&lt;br /&gt;
   \fancyhead{}&lt;br /&gt;
 }&lt;br /&gt;
 \fancyhf{}&lt;br /&gt;
&lt;br /&gt;
The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position.&lt;br /&gt;
&lt;br /&gt;
The footer to display below the coupon (or if there isn't one) is configurable as '''invoice_latexfooter'''.  The footer to show on subsequent pages is '''invoice_latexsmallfooter'''. By default they're both just the company name.&lt;br /&gt;
&lt;br /&gt;
 % Define fancy header/footer for first and subsequent pages&lt;br /&gt;
 \fancyfoot[C]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($coupon) {&lt;br /&gt;
     $OUT .= '\vspace{-\extracouponspace}';&lt;br /&gt;
     $OUT .= '\rule[0.5em]{\textwidth}{\footrulewidth}\\\\';&lt;br /&gt;
     $OUT .= $coupon;&lt;br /&gt;
     $OUT .= '\vspace{'.&lt;br /&gt;
       (defined($couponfootsep) ? $couponfootsep : '0.2in') .&lt;br /&gt;
       '}';&lt;br /&gt;
   }&lt;br /&gt;
   '';&lt;br /&gt;
 --@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]&lt;br /&gt;
 [@-- $footer --@]&lt;br /&gt;
     }[@-- $coupon ? '\vspace{\extracouponspace}' : '' --@]&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]&lt;br /&gt;
 [@-- $smallfooter --@]&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side footer, showing the page number. &amp;quot;emt()&amp;quot; localizes text to the customer's language and escapes for LaTeX. &amp;quot;~&amp;quot; is a non-breaking space character.&lt;br /&gt;
&lt;br /&gt;
 \fancyfoot[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     \small{\thepage~[@-- emt('of') --@]~\pageref{LastPage}}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The left side header, containing the return address and logo on the first page (and nothing on later pages). \returninset is just a horizontal adjustment. \makebox{} tells LaTeX to put its contents into a single box. That's probably unnecessary because the only thing in the box is a table, which is already a single box.&lt;br /&gt;
&lt;br /&gt;
\begin{tabular}{ll} creates a two-column table. In the first column is a &amp;quot;minipage&amp;quot;, which is just a fixed-width container environment (5.5cm here) with its bottom edge aligned to the table cell it's in. In here we put the evaluated &amp;quot;returnaddress&amp;quot; template.&lt;br /&gt;
&lt;br /&gt;
Tables in LaTeX always use &amp;amp; to separate columns and \\ to separate rows. A row must always contain as many columns as in the column spec; otherwise LaTeX will guess about what you mean and will usually get it wrong. \\ at the end of the last row is optional.&lt;br /&gt;
&lt;br /&gt;
The second column contains the logo graphic. See &amp;quot;texdoc graphicx&amp;quot; for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: &amp;quot;\includegraphics[height=0.5in]{[@-- $logo_file --@]}&amp;quot;.&lt;br /&gt;
 &lt;br /&gt;
 \fancyhead[L]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
     \returninset&lt;br /&gt;
     \makebox{&lt;br /&gt;
       \begin{tabular}{ll}&lt;br /&gt;
         \begin{minipage}[b]{5.5cm}&lt;br /&gt;
 [@-- $returnaddress --@]&lt;br /&gt;
         \end{minipage} &amp;amp;&lt;br /&gt;
         \includegraphics{[@-- $logo_file --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     %\includegraphics{[@-- $logo_file --@]} % Uncomment if you want the logo on all pages.&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side header, containing the invoice date, invnum, and custnum in a three-column table with each column centered ({ccc}). &amp;quot;$no_date&amp;quot; and &amp;quot;$no_number&amp;quot; are both enabled by arguments to print_generic() and used when printing statements.&lt;br /&gt;
&lt;br /&gt;
Note that &amp;quot;$date&amp;quot; is already formatted using time2str_local('long'), so something like &amp;quot;Feb 1st, 2017&amp;quot;. The template can call time2str() to format other dates; this is an alias to time2str_local so it will use the customer's language settings and LaTeX-escape the result.&lt;br /&gt;
&lt;br /&gt;
This is followed by a horizontal line spanning the table (\hline), then a row where the middle cell contains the $notice_name (usually &amp;quot;Invoice&amp;quot;) in \huge font, in small caps (\textsc). The \rule{0pt}{5ex} creates a vertical strut (a zero-width rule) which increases the row height to 5ex. Then another \hline.&lt;br /&gt;
&lt;br /&gt;
Note that \hline should never be followed by \\ and will cause errors if it is.&lt;br /&gt;
&lt;br /&gt;
On subsequent pages we show a smaller version without the notice name.&lt;br /&gt;
&lt;br /&gt;
 \fancyhead[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
     \begin{tabular}{ccc}&lt;br /&gt;
     [@-- join(' &amp;amp; ', ( $no_date   ? '' : emt('Invoice date') ),&lt;br /&gt;
                      ( $no_number ? '' : emt('Invoice #')    ),&lt;br /&gt;
                      emt('Customer #')&lt;br /&gt;
              )&lt;br /&gt;
     --@]\\&lt;br /&gt;
     \vspace{0.2cm}&lt;br /&gt;
     \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]} \\\hline&lt;br /&gt;
     \rule{0pt}{5ex} &amp;amp;~~ \huge{\textsc{[@-- emt($notice_name) --@]}} &amp;amp; \\&lt;br /&gt;
     \vspace{-0.2cm}&lt;br /&gt;
      &amp;amp; &amp;amp; \\\hline&lt;br /&gt;
     \end{tabular}&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     \small{&lt;br /&gt;
       \begin{tabular}{lll}&lt;br /&gt;
       [@-- join(' &amp;amp; ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\&lt;br /&gt;
       \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Enable the fancyhdr page style and set the font family.&lt;br /&gt;
&lt;br /&gt;
 \pagestyle{fancy}&lt;br /&gt;
&lt;br /&gt;
 %% Font options are:&lt;br /&gt;
 %%  bch Bitsream Charter&lt;br /&gt;
 %%  put Utopia&lt;br /&gt;
 %%  phv Adobe Helvetica&lt;br /&gt;
 %%  pnc New Century Schoolbook&lt;br /&gt;
 %%  ptm Times&lt;br /&gt;
 %%  pcr Courier&lt;br /&gt;
 &lt;br /&gt;
 \renewcommand{\familydefault}{phv}&lt;br /&gt;
&lt;br /&gt;
=== Line item table macros ===&lt;br /&gt;
&lt;br /&gt;
This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are:&lt;br /&gt;
* Package number&lt;br /&gt;
* Description (6 columns)&lt;br /&gt;
* Price&lt;br /&gt;
&lt;br /&gt;
(The 6 columns are used separately for showing CDRs, if there are any.)&lt;br /&gt;
&lt;br /&gt;
If '''invoice-unitprice''' is on, it's:&lt;br /&gt;
* Package number&lt;br /&gt;
* Description (4 columns)&lt;br /&gt;
* Unit price&lt;br /&gt;
* Quantity&lt;br /&gt;
* Price&lt;br /&gt;
&lt;br /&gt;
According to that setting, create macros for how many columns and how much space to allocate to the description, and for the headers of the Unit Price and Unit Quantity columns.&lt;br /&gt;
&lt;br /&gt;
 % Commands for freeside table header...&lt;br /&gt;
 &lt;br /&gt;
 \newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }&lt;br /&gt;
 \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }&lt;br /&gt;
 \newcommand{\FSunitcolumns}{ [@-- &lt;br /&gt;
   $unitprices&lt;br /&gt;
   ? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &amp;amp;' .&lt;br /&gt;
     '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} &amp;amp; ' &lt;br /&gt;
   : '' --@] }&lt;br /&gt;
&lt;br /&gt;
\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c).&lt;br /&gt;
&lt;br /&gt;
\truncate renders its argument into a space with a maximum width. It will break at word boundaries and insert an ellipsis afterward. Note that if we didn't do this, longtable would either squish the surrounding columns or push them off the page rather than word-wrap the description.&lt;br /&gt;
&lt;br /&gt;
\FSusagehead is the same, but used for usage summary sections that show the number of calls and total minutes. The '''usage_class_summary''' config turns this on.&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FShead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{\FSdescriptioncolumncount}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \FSunitcolumns&lt;br /&gt;
   \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 \newcommand{\FSusagehead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Calls') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Duration') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Amount') --@]} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Next, the commands to actually display line items.&lt;br /&gt;
&lt;br /&gt;
\FSdesc will be called once for each element in the @detail_items array. This takes five arguments (that's the [5] in the declaration) which are the values to put in the pkgnum, description, unit price, quantity, and price columns. If unit prices are off then arguments 3 and 4 will be empty.&lt;br /&gt;
&lt;br /&gt;
\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font.&lt;br /&gt;
&lt;br /&gt;
\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() ''into LaTeX table rows'' (pre-escaped, delimited with &amp;amp;amp;). So, the macro just skips the first column and outputs its argument.&lt;br /&gt;
&lt;br /&gt;
\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column.&lt;br /&gt;
&lt;br /&gt;
Finally, \FSusagedesc is for usage summary sections, and puts the total calls, duration, and amount in the correct columns.&lt;br /&gt;
&lt;br /&gt;
 % ...description...&lt;br /&gt;
 \newcommand{\FSdesc}[5]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{[@-- $unitprices ? '4' : '6' --@]}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{#2}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
 [@-- $unitprices ? '  \multicolumn{1}{r}{\textbf{#3}} &amp;amp;'.&amp;quot;\n&amp;quot;.&lt;br /&gt;
                    '  \multicolumn{1}{r}{\textbf{#4}} &amp;amp;'.&amp;quot;\n&amp;quot;&lt;br /&gt;
                  : ''&lt;br /&gt;
 --@]&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#5}}\\&lt;br /&gt;
 }&lt;br /&gt;
 % ...extended description...&lt;br /&gt;
 \newcommand{\FSextdesc}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{\small{[[User:Mark|Mark]] ([[User talk:Mark|talk]])#1}}&lt;br /&gt;
   } \\&lt;br /&gt;
 }&lt;br /&gt;
 % ...call detail (multiple columns already)...&lt;br /&gt;
 \newcommand{\FScalldetail}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   [[User:Mark|Mark]] ([[User talk:Mark|talk]])#1&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
 }&lt;br /&gt;
 % ...and total line items (which use the full 12.8cm length, ignoring&lt;br /&gt;
 % unitprice/quantity&lt;br /&gt;
 \newcommand{\FStotaldesc}[2]{&lt;br /&gt;
   &amp;amp; \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{#1}&lt;br /&gt;
   } &amp;amp; #2\\&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 % ...usage class summary&lt;br /&gt;
 \newcommand{\FSusagedesc}[4]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#2}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#3}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#4}}&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Main document ===&lt;br /&gt;
&lt;br /&gt;
First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line.&lt;br /&gt;
&lt;br /&gt;
 \begin{document}&lt;br /&gt;
 % Headers and footers defined for the first page&lt;br /&gt;
 \addressinset \rule{0.5cm}{0cm} &lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{7.0cm}&lt;br /&gt;
 \vspace{[@-- defined($addresssep) ? $addresssep : '0.25cm' --@]}&lt;br /&gt;
 \textbf{[@-- $payname --@]}\\&lt;br /&gt;
 \addressline{[@-- $company --@]}&lt;br /&gt;
 \addressline{[@-- $address1 --@]}&lt;br /&gt;
 \addressline{[@-- $address2 --@]}&lt;br /&gt;
 \addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}&lt;br /&gt;
 \addressline{[@-- $country --@]}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
&lt;br /&gt;
Make another minipage for the service address, terms, and PO number. $ship_enable is true if the global '''invoice-ship_address''' or per-customer &amp;quot;invoice_ship_address&amp;quot; flag is on.&lt;br /&gt;
&lt;br /&gt;
The initial \hfill spreads out the two minipages so that they go all the way to the margins. The 'terms' and 'po_line' should probably be in \addressline{} macros but currently aren't.&lt;br /&gt;
&lt;br /&gt;
After that, skip some vertical space. (If you need more vertical space for an invoice format, the 1.5cm here can be cut.)&lt;br /&gt;
&lt;br /&gt;
 \hfill&lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{6.4cm}&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($ship_enable) {&lt;br /&gt;
     $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_company}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address1}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address2}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_city, $ship_state~~$ship_zip}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_country}&amp;quot;;&lt;br /&gt;
     $OUT .= '~\\\\';&lt;br /&gt;
   }else{&lt;br /&gt;
     $OUT .= '';&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
 \begin{flushright}&lt;br /&gt;
 [@-- $terms ? emt('Terms') . ': ' . emt($terms) : '' --@]\\&lt;br /&gt;
 [@-- $po_line --@]\\&lt;br /&gt;
 \end{flushright}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
 \vspace{1.5cm}&lt;br /&gt;
&lt;br /&gt;
Insert the [[Summary]] if there is one. The last thing in the summary template is a page break.&lt;br /&gt;
 %&lt;br /&gt;
 [@-- $summary --@]&lt;br /&gt;
 %&lt;br /&gt;
&lt;br /&gt;
=== Sections ===&lt;br /&gt;
&lt;br /&gt;
Start creating sections, according to the @sections array. Section entries contain the following keys:&lt;br /&gt;
&lt;br /&gt;
* description&lt;br /&gt;
* category (pkg_category.categoryname, for package sections by category)&lt;br /&gt;
* location (hashref of location fields, for package sections by location)&lt;br /&gt;
* pretotal (formatted with \FStotaldesc, before detail lines)&lt;br /&gt;
* subtotal (formatted with \FStotaldesc, at the end)&lt;br /&gt;
* posttotal (line to display after the end of the section; used for &amp;quot;Balance Due&amp;quot; message and a few other things)&lt;br /&gt;
* sort_weight (sort order, but also does complicated things)&lt;br /&gt;
&lt;br /&gt;
and optionally some flags:&lt;br /&gt;
* post_total (put the section at the end of the invoice)&lt;br /&gt;
* summarized (omit the section entirely)&lt;br /&gt;
* usage_section (use \FSusagedesc instead of \FSdesc, see above)&lt;br /&gt;
* no_subtotal (omit the subtotal line)&lt;br /&gt;
&lt;br /&gt;
If '''invoice_sections''' is enabled: the $multisection flag in print_generic() is turned on, and @sections is filled by calling FS::Template_Mixin::_items_sections, which makes a section for each package category. Line items for packages go into the package category's section. '''invoice_sections_method''' can be used to divide sections by location rather than by category. In addition, sections are created for previous invoices, taxes, and &amp;quot;adjustments&amp;quot; (credits and payments to this invoice).&lt;br /&gt;
&lt;br /&gt;
If not: a single section is created, with empty description, containing previous invoices, current charges, and adjustments (after the total). '''previous_balance-section''' will make a previous invoices section anyway.&lt;br /&gt;
&lt;br /&gt;
Additional sections are created in some cases. '''svc_phone_sections''' creates a section for each phone line (see RT#6592; note that this customer is no longer active). '''voip-cust_accountcode_cdr''' creates a section for each accountcode (see RT#12181). '''discount-show_available''' creates a section with information on available discounts. '''usage_class_summary''' creates a section with usage subtotals.&lt;br /&gt;
&lt;br /&gt;
'''finance_pkgclass''' specifies the package class containing &amp;quot;finance charges&amp;quot;, which is handled specially. That class's category name is passed to the template as $finance_section, and the sum of charges in that category is passed as $finance_amount. That amount is broken out from the &amp;quot;Current charges&amp;quot; line in the summary section. On a multisection summary-format invoice, the finance charges will also ''not'' be shown in the body of the invoice. I don't know why.&lt;br /&gt;
&lt;br /&gt;
Loop over all sections (except for the finance charges, maybe). Inside the loop we'll do everything by appending to $OUT.&lt;br /&gt;
&lt;br /&gt;
 \section*{}&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section ( grep { !$summary || $_-&amp;gt;{description} ne $finance_section } @sections ) {&lt;br /&gt;
 &lt;br /&gt;
We're going to do many things if &amp;quot;!$summary&amp;quot;. If there's a summary section, the pretotals and posttotals are redundant so skip them.&lt;br /&gt;
&lt;br /&gt;
    if ($section-&amp;gt;{'pretotal'} &amp;amp;&amp;amp; !$summary) {&lt;br /&gt;
      $OUT .= '\begin{flushright}';&lt;br /&gt;
      $OUT .= '\large\textsc{'. $section-&amp;gt;{'pretotal'}. '}\\\\';&lt;br /&gt;
      $OUT .= '\\end{flushright}';&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
Start a new page if this is a &amp;quot;post_total&amp;quot; section. As far as I know these are used only for usage details. I think the 'summarized' flag is no longer used for anything.&lt;br /&gt;
&lt;br /&gt;
The section heading is going to be a caption placed inside the main table. \captionsetup sets properties for the caption (left justified, bold, small caps, Large size). If this is the first page, also tell longtable (via \LTextracouponspace) to shorten the effective length of the page so that it doesn't overprint the coupon.&lt;br /&gt;
&lt;br /&gt;
    $OUT .= '\pagebreak' if $section-&amp;gt;{'post_total'};&lt;br /&gt;
    unless ($section-&amp;gt;{'summarized'} ) {&lt;br /&gt;
      $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';&lt;br /&gt;
      $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'&lt;br /&gt;
        if $coupon;&lt;br /&gt;
&lt;br /&gt;
Start the longtable that will contain the section's line items. Eight columns, pkgnum (centered), description and optional unitprice/quantity (left), then price (right).&lt;br /&gt;
&lt;br /&gt;
\caption* produces a table caption without prefixing it with &amp;quot;Table 1&amp;quot;. The caption is the section description, or 'Charges' if it's null (except that if packages are sectioned by location, we put one together from the location fields).&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\begin{longtable}{cllllllr}';&lt;br /&gt;
      $OUT .= '\caption*{ ';&lt;br /&gt;
      if ($section-&amp;gt;{'location'}) {&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'label_prefix'}. ': '&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'label_prefix'});&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'address1'};&lt;br /&gt;
        $OUT .= ', ' . $section-&amp;gt;{'location'}{'address2'}&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'address2'});&lt;br /&gt;
        $OUT .= ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'city'} . ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'state'} . '~' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'zip'};&lt;br /&gt;
      } elsif ( $section-&amp;gt;{'description'} ) {&lt;br /&gt;
        $OUT .= ($section-&amp;gt;{'description'});&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= emt('Charges');&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '}\\\\';&lt;br /&gt;
&lt;br /&gt;
The point of a longtable is that it can span multiple pages, including printing head/foot rows at the top/bottom of each page automatically. The protocol for setting these up is to specify the first head, per-page head, per-page foot, and last foot, then the rest of the table contents. Here, we set the first head to be \FShead (or \FSusagehead). \endfirsthead means we're done specifying it.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;header_generator&amp;quot; and other foo_generators are from an old mechanism that injected callbacks into the template data. This is strongly deprecated.&lt;br /&gt;
&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endfirsthead';&lt;br /&gt;
&lt;br /&gt;
The per-page head: a row with a &amp;quot;Continued&amp;quot; message, then \FShead again. The per-page foot is the same.&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endhead';&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';&lt;br /&gt;
      $OUT .= '\endfoot';&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
&lt;br /&gt;
The final foot for the section. For multisection invoices this contains the section subtotal:&lt;br /&gt;
&lt;br /&gt;
      if (scalar(@sections) &amp;gt; 1 and !$section-&amp;gt;{no_subtotal}) {&lt;br /&gt;
        if ($section-&amp;gt;{total_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{total_generator}}($section);&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FStotaldesc{' . $section-&amp;gt;{'description'} . ' Total}' .&lt;br /&gt;
                  '{' . $section-&amp;gt;{'subtotal'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
Single-section invoices use &amp;quot;@total_items&amp;quot; for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot.&lt;br /&gt;
&lt;br /&gt;
      #if ($section == $sections[$#sections]) {&lt;br /&gt;
        foreach my $line (grep {$_-&amp;gt;{section}-&amp;gt;{description} eq $section-&amp;gt;{description}} @total_items) {&lt;br /&gt;
          if ($section-&amp;gt;{total_line_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{total_line_generator}}($line);&lt;br /&gt;
          } else {&lt;br /&gt;
            $OUT .= '\FStotaldesc{' . $line-&amp;gt;{'total_item'} . '}' .&lt;br /&gt;
                    '{' . $line-&amp;gt;{'total_amount'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      #}&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
      $OUT .= '\endlastfoot';&lt;br /&gt;
&lt;br /&gt;
Line items are passed in @detail_items. A line item entry can contain:&lt;br /&gt;
&lt;br /&gt;
* section (reference)&lt;br /&gt;
* ref (pkgnum)&lt;br /&gt;
* description&lt;br /&gt;
* ext_description (arrayref of additional lines)&lt;br /&gt;
* quantity&lt;br /&gt;
* amount&lt;br /&gt;
* duration (for usage summary sections)&lt;br /&gt;
* pkgpart&lt;br /&gt;
* unit_amount (unit price)&lt;br /&gt;
* locationnum&lt;br /&gt;
* svc_label (the primary service's label)&lt;br /&gt;
&lt;br /&gt;
Line items are created in several places in Template_Mixin and TemplateItem_Mixin, most importantly _items_cust_bill_pkg.&lt;br /&gt;
&lt;br /&gt;
@detail_items is a single array; we find the lines belonging to this section by checking their 'section' element. If there's only one section, process all the lines.&lt;br /&gt;
&lt;br /&gt;
      my $lastref = 0;&lt;br /&gt;
      foreach my $line (&lt;br /&gt;
        grep { ( scalar( @sections ) &amp;gt; 1 &lt;br /&gt;
               ? $section-&amp;gt;{'description'} eq $_-&amp;gt;{'section'}-&amp;gt;{'description'}&lt;br /&gt;
               : 1&lt;br /&gt;
             ) }&lt;br /&gt;
        @detail_items )&lt;br /&gt;
      {&lt;br /&gt;
        my $ext_description = $line-&amp;gt;{'ext_description'};&lt;br /&gt;
  &lt;br /&gt;
        # Don't break-up small packages.&lt;br /&gt;
        my $rowbreak = @$ext_description &amp;lt; 5 ? '*' : '';&lt;br /&gt;
&lt;br /&gt;
$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum.&lt;br /&gt;
&lt;br /&gt;
If this is a usage summary section, convert the duration to minutes/seconds and call \FSusagedesc, passing the description, quantity (number of calls), duration, and total price.&lt;br /&gt;
&lt;br /&gt;
If it's a normal section, call \FSdesc, passing the description, unit price, quantity, and amount. If unit prices aren't in use, or there isn't one on this line item, then leave unit price and quantity blank.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;$rowbreak&amp;quot; is a flag for whether to allow a page break. In general we want to keep line items and their ext_description lines together, unless there are a lot of description lines. So if there are fewer than 5 of them, we set $rowbreak = '*', which after \FSdesc{} is evaluated, results in the table row delimiter being &amp;quot;\\*&amp;quot;. This tells LaTeX to avoid putting a page break immediately after this row. Probably we should set $rowbreak = '' on the last description line to explicitly allow page breaks between packages.&lt;br /&gt;
&lt;br /&gt;
        $OUT .= &amp;quot;\\hline\n&amp;quot; if (($line-&amp;gt;{'ref'} || 0) ne $lastref);&lt;br /&gt;
        if ($section-&amp;gt;{description_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{description_generator}}($line);&lt;br /&gt;
        } elsif ($section-&amp;gt;{usage_section}) {&lt;br /&gt;
          my $minutes = sprintf('%d', $line-&amp;gt;{'duration'} / 60);&lt;br /&gt;
          my $seconds = $line-&amp;gt;{'duration'} % 60;&lt;br /&gt;
          $OUT .= '\FSusagedesc&lt;br /&gt;
            {' . $line-&amp;gt;{'description'} . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'quantity'} . '}&lt;br /&gt;
            {' . $minutes . 'm ' . $seconds . 's' . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'amount'} . '}';&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FSdesc'.&lt;br /&gt;
                  '{}'.&lt;br /&gt;
                  '{' . $line-&amp;gt;{'description'} . '}' ;&lt;br /&gt;
          if ( $unitprices and length($line-&amp;gt;{'unit_amount'}) ) {&lt;br /&gt;
            # then show the unit amount and quantity&lt;br /&gt;
            $OUT .= &lt;br /&gt;
                '{\\dollar' . $line-&amp;gt;{'unit_amount'} . '}'.&lt;br /&gt;
                '{'         . $line-&amp;gt;{'quantity'}    . '}';&lt;br /&gt;
          } else {&lt;br /&gt;
            # leave those columns blank&lt;br /&gt;
            $OUT .= '{}{}';&lt;br /&gt;
          }&lt;br /&gt;
          $OUT .= '{\\dollar' . $line-&amp;gt;{'amount'} . &amp;quot;}${rowbreak}\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        $lastref = $line-&amp;gt;{'ref'} || 0;&lt;br /&gt;
&lt;br /&gt;
Output ext_description lines. We assume that any of them that contain an unescaped &amp;quot;&amp;amp;amp;&amp;quot; are call details and should use \FScalldetail. Otherwise they use \FSextdesc.&lt;br /&gt;
&lt;br /&gt;
        foreach my $ext_desc (@$ext_description) {&lt;br /&gt;
          if ($section-&amp;gt;{extended_description_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{extended_description_generator}}($ext_desc);&lt;br /&gt;
          } elsif ( $ext_desc !~ /[^\\]&amp;amp;/ ) {&lt;br /&gt;
            $OUT .= '\FSextdesc{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          } else { # call detail&lt;br /&gt;
            $OUT .= '\FScalldetail{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
End the longtable. If there is a posttotal item in this section (the final Balance Due / Credit Balance message, previous invoice aging, a few other things) then show it right-justified, in the same formatting used for the caption.&lt;br /&gt;
&lt;br /&gt;
       $OUT .= '\end{longtable}';&lt;br /&gt;
     }&lt;br /&gt;
     if ($section-&amp;gt;{'posttotal'}) {&lt;br /&gt;
       $OUT .= '\begin{flushright}';&lt;br /&gt;
       $OUT .= '\normalfont\large\bfseries\textsc{'. $section-&amp;gt;{'posttotal'}. '}\\\\';&lt;br /&gt;
       $OUT .= '\\end{flushright}';&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
&lt;br /&gt;
For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.)&lt;br /&gt;
&lt;br /&gt;
There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space.&lt;br /&gt;
&lt;br /&gt;
However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the ''strut'' is at the bottom of the page, keeping the coupon space clear.&lt;br /&gt;
&lt;br /&gt;
 --@]&lt;br /&gt;
 \vfill&lt;br /&gt;
 \begin{minipage}[t]{\textwidth}&lt;br /&gt;
   [@-- length($summary)&lt;br /&gt;
          ? ''&lt;br /&gt;
         : ( $smallernotes&lt;br /&gt;
               ? '\scriptsize{ '.$notes.' }'&lt;br /&gt;
               : $notes&lt;br /&gt;
           )&lt;br /&gt;
   --@]&lt;br /&gt;
   [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' : '' --@]&lt;br /&gt;
 \end{minipage}&lt;br /&gt;
 \end{document}&lt;br /&gt;
&lt;br /&gt;
That's the end of the invoice.&lt;br /&gt;
&lt;br /&gt;
== The summary template ==&lt;br /&gt;
&lt;br /&gt;
Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes.&lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{ll}&lt;br /&gt;
 \begin{minipage}{6.4cm}&lt;br /&gt;
 \begin{tabular}{m{0cm}m{6.4cm}}&lt;br /&gt;
 \rule{0cm}{10cm}&amp;amp;\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} &amp;amp;&lt;br /&gt;
&lt;br /&gt;
On the right side, place a 2cm horizontal strut followed by a 12.8cm minipage. This will contain the billing summary.&lt;br /&gt;
&lt;br /&gt;
 \rule{2cm}{0cm}&lt;br /&gt;
 \begin{minipage}{12.8cm}&lt;br /&gt;
&lt;br /&gt;
The billing summary itself is a two-column table, one for labels and one for amounts. The section headings are just labels with no amounts, with lines underneath.&lt;br /&gt;
&lt;br /&gt;
'true_previous_balance' is, as far as we can determine it, the balance that was printed on the customer's previous invoice. It's the sum of all the customer's transactions (invoices, credits, payments, refunds) dated before that invoice, plus the invoice amount.&lt;br /&gt;
&lt;br /&gt;
'balance_adjustments' is the sum of payments and credits received after the previous invoice, but applied to invoices before this current invoice. 'true_previous_balance' - 'balance_adjustments' is the total amount owed on all past invoices.&lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{lr}&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of Previous Balance and Payments}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{Previous Balance}&amp;amp;\textbf{\dollar[@-- $true_previous_balance --@]}\\&lt;br /&gt;
 \textbf{Payments}&amp;amp;\textbf{\dollar[@-- $balance_adjustments --@]}\\&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Balance Outstanding}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of New Charges}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
&lt;br /&gt;
@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. This is followed with \cline{2-2}, which puts a horizontal line under the second column only, and then a row for the total of new charges.&lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section (@summary_subtotals) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'} ? $section-&amp;gt;{'description'} : 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
   $OUT .= '\cline{2-2}';&lt;br /&gt;
 --@]&lt;br /&gt;
 \textbf{New Charges Total}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Invoice Summary}} &amp;amp; \\&lt;br /&gt;
 &amp;amp; \\&lt;br /&gt;
 \textbf{Previous Past Due Charges}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\&lt;br /&gt;
 \textbf{Finance charges on overdue amount}&amp;amp;\textbf{\dollar[@-- $finance_amount --@]}\\&lt;br /&gt;
 \textbf{New Charges}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
&lt;br /&gt;
Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the ''current'' invoice.&lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   #false laziness w/invoice_htmlsummary and above&lt;br /&gt;
   foreach my $section ( grep $_-&amp;gt;{adjust_section}, @sections ) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'} ? $section-&amp;gt;{'description'} : 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Finally, the customer's balance including the current invoice, and then close the table and minipage, and force a page break so the line items can start on a new page.&lt;br /&gt;
&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Total Amount Due}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $balance) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} \\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \newpage&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Invoice modes ==&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9614</id>
		<title>Invoice template documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Invoice_template_documentation&amp;diff=9614"/>
				<updated>2017-01-10T22:01:57Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;= General notes =  Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the st...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= General notes =&lt;br /&gt;
&lt;br /&gt;
Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template.&lt;br /&gt;
&lt;br /&gt;
The main function for generating the LaTeX code is FS::Template_Mixin::print_generic. This is a method of the cust_bill object (and also of quotation objects), and takes several arguments described in the perldoc. Most of what this function does is set up the &amp;quot;%invoice_data&amp;quot; hash, which gets imported into local variable space when evaluating the template.&lt;br /&gt;
&lt;br /&gt;
print_generic() actually constructs several templates: 'notes', 'coupon', 'footer', 'smallfooter', 'watermark', and sometimes 'summary'. Exactly which config variable is loaded for each of these is decided in Template_Mixin; look for the line that starts &amp;quot;my $tc&amp;quot;. It's '''invoice_$format$part''' (where $format is either &amp;quot;latex&amp;quot; or &amp;quot;html&amp;quot; and $part is &amp;quot;notes&amp;quot;, &amp;quot;coupon&amp;quot;, etc.). Each template is evaluated, with %invoice_data as arguments, then put back into %invoice_data as &amp;quot;$notes&amp;quot;, &amp;quot;$coupon&amp;quot;, etc. The main template contains code fragments to include them.&lt;br /&gt;
&lt;br /&gt;
To further complicate things, all of these elements have locale overrides AND invoice mode overrides. If print_generic() was called with a 'mode' argument, those templates will be fetched from the invoice mode + customer locale. If not, they'll be taken from the system config for the customer's agent and locale.&lt;br /&gt;
&lt;br /&gt;
You can obtain the generated LaTeX code for a particular invoice with ''view/cust_bill-tex.cgi'', which takes all the same parameters as ''view/cust_bill.cgi''.&lt;br /&gt;
&lt;br /&gt;
The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside.&lt;br /&gt;
&lt;br /&gt;
One useful tool for editing a template is Overleaf (https://www.overleaf.com/), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of &amp;quot;edit template, set config, download and examine PDF&amp;quot;, especially since it will display LaTeX errors in a semi-useful way.&lt;br /&gt;
&lt;br /&gt;
== Positioning ==&lt;br /&gt;
&lt;br /&gt;
This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the &amp;quot;textpos&amp;quot; package.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;textpos&amp;quot; requires you to set up a grid system, like this:&lt;br /&gt;
&lt;br /&gt;
 \usepackage[absolute]{textpos}&lt;br /&gt;
 \setlength{\TPHorizModule}{1in}&lt;br /&gt;
 \setlength{\TPVertModule}{1in}&lt;br /&gt;
&lt;br /&gt;
The environment for a fixed position on the page is &amp;quot;textblock&amp;quot;. The syntax is odd; notice parentheses rather than braces, and numbers without units.&lt;br /&gt;
&lt;br /&gt;
 \begin{textblock}{3.25}(0.625, 0.5)&lt;br /&gt;
   \footnotesize{&lt;br /&gt;
     This text is in a box starting 0.625in left and 0.5in down from the page&lt;br /&gt;
     origin, in a box 3.25in wide. It will fit inside an envelope window.&lt;br /&gt;
   }&lt;br /&gt;
 \end{textblock}&lt;br /&gt;
&lt;br /&gt;
The textblock behaves a lot like a minipage, and can contain almost anything; in particular, \includegraphics works. It's positioned after everything else on the page, which means there's no protection against overprinting anything.&lt;br /&gt;
&lt;br /&gt;
== The standard invoice template ==&lt;br /&gt;
&lt;br /&gt;
=== Preamble ===&lt;br /&gt;
&lt;br /&gt;
Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document.&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;fancyhdr&amp;quot; supplies the \fancyhead and \fancyfoot commands to place left, right, and center headers and footers.&lt;br /&gt;
* &amp;quot;lastpage&amp;quot; creates the LastPage reference for displaying &amp;quot;page X of Y&amp;quot; labels.&lt;br /&gt;
* &amp;quot;ifthen&amp;quot; provides the \ifthenelse command.&lt;br /&gt;
* &amp;quot;array&amp;quot; provides support for custom table column formats.&lt;br /&gt;
* &amp;quot;longtable&amp;quot; provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the &amp;quot;\LTcouponspace&amp;quot; variable.&lt;br /&gt;
* &amp;quot;afterpage&amp;quot; was removed from the template in May 2005 and we can remove the package at some point.&lt;br /&gt;
* &amp;quot;multirow&amp;quot; is used to make a table cell in the coupon to hold the return address.&lt;br /&gt;
* &amp;quot;bigstrut&amp;quot; appears not to be used.&lt;br /&gt;
* &amp;quot;truncate&amp;quot; provides a command to apply a width limit to a line of text, so that anything past that gets replaced with an ellipsis. This is highly encouraged for things like package descriptions that could otherwise overflow their table cells.&lt;br /&gt;
* &amp;quot;graphicx&amp;quot; provides the \includegraphics command for inserting the EPS of the logo.&lt;br /&gt;
* &amp;quot;inputenc&amp;quot; and &amp;quot;fontenc&amp;quot; make LaTeX play nicely with UTF-8 characters.&lt;br /&gt;
* &amp;quot;background&amp;quot; is used to create watermarks (such as are used for some past-due notices, or voided invoices). The \backgroundsetup command takes a TikZ argument list, which is extremely powerful. See &amp;quot;texdoc pgf&amp;quot; for all the horrifying details.&lt;br /&gt;
&lt;br /&gt;
Useful things to add here:&lt;br /&gt;
&lt;br /&gt;
* &amp;quot;textpos&amp;quot;: see above.&lt;br /&gt;
* &amp;quot;geometry&amp;quot; to manage the margins and headers in a centralized way. It also takes the [showframe] option, which will draw lines to visualize the page layout.&lt;br /&gt;
* &amp;quot;xcolor&amp;quot; if you want color.&lt;br /&gt;
* &amp;quot;tabularx&amp;quot; and/or &amp;quot;tabulary&amp;quot; add more options for setting table column widths.&lt;br /&gt;
&lt;br /&gt;
 \documentclass[letterpaper]{article}&lt;br /&gt;
 &lt;br /&gt;
 \usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}&lt;br /&gt;
 \usepackage[breakwords]{truncate} % to avoid overflowing boxes&lt;br /&gt;
 \usepackage{graphicx}     % required for logo graphic&lt;br /&gt;
 \usepackage[utf8]{inputenc}             % multilanguage support&lt;br /&gt;
 \usepackage[T1]{fontenc}&lt;br /&gt;
 [@-- if ( length($watermark) ) {&lt;br /&gt;
   $OUT .= '&lt;br /&gt;
 \usepackage{background}&lt;br /&gt;
 \backgroundsetup{&lt;br /&gt;
   placement=center,&lt;br /&gt;
   opacity=0.25,&lt;br /&gt;
   color=black,&lt;br /&gt;
   angle=0,&lt;br /&gt;
   contents=' . $watermark . '&lt;br /&gt;
 }';&lt;br /&gt;
 }&lt;br /&gt;
 '';&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Set up dimensions. Most of these were determined empirically and really ought to use the &amp;quot;geometry&amp;quot; package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight.&lt;br /&gt;
&lt;br /&gt;
See [https://en.wikibooks.org/wiki/LaTeX/Page_Layout] and [http://mirrors.ctan.org/macros/latex/base/letter.pdf texdoc letter] for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that.&lt;br /&gt;
&lt;br /&gt;
If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container.&lt;br /&gt;
&lt;br /&gt;
Commands for manipulating length variables: \newlength declares them (and all of these are predeclared in the document stylesheet); \setlength sets them to a value; \addtolength adds to them.&lt;br /&gt;
&lt;br /&gt;
If you're adjusting margins, it can be helpful to add &amp;quot;\usepackage{layout}&amp;quot; to the preamble, and then &amp;quot;\newpage\layout&amp;quot; before the final &amp;quot;\end{document}&amp;quot;. This will print a diagram showing the layout boxes and their dimensions.&lt;br /&gt;
&lt;br /&gt;
\LTchunksize is an internal variable in longtable for how many rows to process at a time. There's probably no reason to change it.&lt;br /&gt;
&lt;br /&gt;
 \addtolength{\voffset}{-0.0cm}    % top margin to top of header&lt;br /&gt;
 \addtolength{\hoffset}{-0.6cm}    % left margin on page&lt;br /&gt;
 \addtolength{\topmargin}{[@-- defined($topmargin) ? $topmargin : '-1.00cm' --@]}&lt;br /&gt;
 \setlength{\headheight}{2.0cm}    % height of header&lt;br /&gt;
 \setlength{\headsep}{[@-- defined($headsep) ? $headsep : '1.0cm' --@]}&lt;br /&gt;
 \setlength{\footskip}{1.0cm}    % bottom of footer from bottom of text&lt;br /&gt;
 &lt;br /&gt;
 %\addtolength{\textwidth}{2.1in}      % width of text&lt;br /&gt;
 \setlength{\textwidth}{19.5cm}&lt;br /&gt;
 \setlength{\textheight}{[@-- defined($textheight) ? $textheight : '19.5cm' --@]}&lt;br /&gt;
 \setlength{\oddsidemargin}{-0.9cm}  % odd page left margin&lt;br /&gt;
 \setlength{\evensidemargin}{-0.9cm}   % even page left margin&lt;br /&gt;
 &lt;br /&gt;
 \LTchunksize=40&lt;br /&gt;
&lt;br /&gt;
$coupon is a local variable containing the entire generated content of the [[Invoice_coupon_template|invoice payment coupon]]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string.&lt;br /&gt;
&lt;br /&gt;
\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position).&lt;br /&gt;
&lt;br /&gt;
\extracouponspace is a config setting for how much space to reserve for the coupon. We will &amp;quot;shorten&amp;quot; the first page's \textheight by that much, which moves the starting position of the header up.&lt;br /&gt;
&lt;br /&gt;
 \renewcommand{\headrulewidth}{0pt}&lt;br /&gt;
 \renewcommand{\footrulewidth}{1pt}&lt;br /&gt;
 &lt;br /&gt;
 \renewcommand{\footrule}{&lt;br /&gt;
 [@--&lt;br /&gt;
   $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : '';&lt;br /&gt;
 --@]&lt;br /&gt;
   {&lt;br /&gt;
   }&lt;br /&gt;
   {&lt;br /&gt;
     \vbox to 0pt{\rule{\headwidth}{\footrulewidth}\vss}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\extracouponspace}{[@-- defined($extracouponspace) ? $extracouponspace : '2.7in' --@]}&lt;br /&gt;
&lt;br /&gt;
Positioning mailing addresses. This is a bad way to do it and I'd recommend using the &amp;quot;textpos&amp;quot; package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout.&lt;br /&gt;
 &lt;br /&gt;
 % Adjust the inset of the mailing address&lt;br /&gt;
 \newcommand{\addressinset}[1][]{\hspace{1.0cm}}&lt;br /&gt;
 &lt;br /&gt;
 % Adjust the inset of the return address and logo&lt;br /&gt;
 \newcommand{\returninset}[1][]{\hspace{-0.25cm}}&lt;br /&gt;
 &lt;br /&gt;
 % New command for address lines i.e. skip them if blank&lt;br /&gt;
 \newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}&lt;br /&gt;
 &lt;br /&gt;
 % Inserts dollar symbol&lt;br /&gt;
 \newcommand{\dollar}[1][]{\symbol{36}}&lt;br /&gt;
&lt;br /&gt;
=== Headers/footers ===&lt;br /&gt;
&lt;br /&gt;
&amp;quot;fancyhdr&amp;quot; allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer.&lt;br /&gt;
&lt;br /&gt;
These commands are supposed to remove the default &amp;quot;plain&amp;quot; style page footer, but we don't use that page style so it's probably not needed.&lt;br /&gt;
&lt;br /&gt;
 % Remove plain style header/footer&lt;br /&gt;
 \fancypagestyle{plain}{&lt;br /&gt;
   \fancyhead{}&lt;br /&gt;
 }&lt;br /&gt;
 \fancyhf{}&lt;br /&gt;
&lt;br /&gt;
The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position.&lt;br /&gt;
&lt;br /&gt;
The footer to display below the coupon (or if there isn't one) is configurable as '''invoice_latexfooter'''.  The footer to show on subsequent pages is '''invoice_latexsmallfooter'''. By default they're both just the company name.&lt;br /&gt;
&lt;br /&gt;
 % Define fancy header/footer for first and subsequent pages&lt;br /&gt;
 \fancyfoot[C]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($coupon) {&lt;br /&gt;
     $OUT .= '\vspace{-\extracouponspace}';&lt;br /&gt;
     $OUT .= '\rule[0.5em]{\textwidth}{\footrulewidth}\\\\';&lt;br /&gt;
     $OUT .= $coupon;&lt;br /&gt;
     $OUT .= '\vspace{'.&lt;br /&gt;
       (defined($couponfootsep) ? $couponfootsep : '0.2in') .&lt;br /&gt;
       '}';&lt;br /&gt;
   }&lt;br /&gt;
   '';&lt;br /&gt;
 --@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]&lt;br /&gt;
 [@-- $footer --@]&lt;br /&gt;
     }[@-- $coupon ? '\vspace{\extracouponspace}' : '' --@]&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]&lt;br /&gt;
 [@-- $smallfooter --@]&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side footer, showing the page number. &amp;quot;emt()&amp;quot; localizes text to the customer's language and escapes for LaTeX. &amp;quot;~&amp;quot; is a non-breaking space character.&lt;br /&gt;
&lt;br /&gt;
 \fancyfoot[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     \small{\thepage~[@-- emt('of') --@]~\pageref{LastPage}}&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The left side header, containing the return address and logo on the first page (and nothing on later pages). \returninset is just a horizontal adjustment. \makebox{} tells LaTeX to put its contents into a single box. That's probably unnecessary because the only thing in the box is a table, which is already a single box.&lt;br /&gt;
&lt;br /&gt;
\begin{tabular}{ll} creates a two-column table. In the first column is a &amp;quot;minipage&amp;quot;, which is just a fixed-width container environment (5.5cm here) with its bottom edge aligned to the table cell it's in. In here we put the evaluated &amp;quot;returnaddress&amp;quot; template.&lt;br /&gt;
&lt;br /&gt;
Tables in LaTeX always use &amp;amp; to separate columns and \\ to separate rows. A row must always contain as many columns as in the column spec; otherwise LaTeX will guess about what you mean and will usually get it wrong. \\ at the end of the last row is optional.&lt;br /&gt;
&lt;br /&gt;
The second column contains the logo graphic. See &amp;quot;texdoc graphicx&amp;quot; for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: &amp;quot;\includegraphics[height=0.5in]{[@-- $logo_file --@]}&amp;quot;.&lt;br /&gt;
 &lt;br /&gt;
 \fancyhead[L]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
     \returninset&lt;br /&gt;
     \makebox{&lt;br /&gt;
       \begin{tabular}{ll}&lt;br /&gt;
         \begin{minipage}[b]{5.5cm}&lt;br /&gt;
 [@-- $returnaddress --@]&lt;br /&gt;
         \end{minipage} &amp;amp;&lt;br /&gt;
         \includegraphics{[@-- $logo_file --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     %\includegraphics{[@-- $logo_file --@]} % Uncomment if you want the logo on all pages.&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The right side header, containing the invoice date, invnum, and custnum in a three-column table with each column centered ({ccc}). &amp;quot;$no_date&amp;quot; and &amp;quot;$no_number&amp;quot; are both enabled by arguments to print_generic() and used when printing statements.&lt;br /&gt;
&lt;br /&gt;
Note that &amp;quot;$date&amp;quot; is already formatted using time2str_local('long'), so something like &amp;quot;Feb 1st, 2017&amp;quot;. The template can call time2str() to format other dates; this is an alias to time2str_local so it will use the customer's language settings and LaTeX-escape the result.&lt;br /&gt;
&lt;br /&gt;
This is followed by a horizontal line spanning the table (\hline), then a row where the middle cell contains the $notice_name (usually &amp;quot;Invoice&amp;quot;) in \huge font, in small caps (\textsc). The \rule{0pt}{5ex} creates a vertical strut (a zero-width rule) which increases the row height to 5ex. Then another \hline.&lt;br /&gt;
&lt;br /&gt;
Note that \hline should never be followed by \\ and will cause errors if it is.&lt;br /&gt;
&lt;br /&gt;
On subsequent pages we show a smaller version without the notice name.&lt;br /&gt;
&lt;br /&gt;
 \fancyhead[R]{&lt;br /&gt;
   \ifthenelse{\equal{\thepage}{1}}&lt;br /&gt;
   { % First page&lt;br /&gt;
     \begin{tabular}{ccc}&lt;br /&gt;
     [@-- join(' &amp;amp; ', ( $no_date   ? '' : emt('Invoice date') ),&lt;br /&gt;
                      ( $no_number ? '' : emt('Invoice #')    ),&lt;br /&gt;
                      emt('Customer #')&lt;br /&gt;
              )&lt;br /&gt;
     --@]\\&lt;br /&gt;
     \vspace{0.2cm}&lt;br /&gt;
     \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]} \\\hline&lt;br /&gt;
     \rule{0pt}{5ex} &amp;amp;~~ \huge{\textsc{[@-- emt($notice_name) --@]}} &amp;amp; \\&lt;br /&gt;
     \vspace{-0.2cm}&lt;br /&gt;
      &amp;amp; &amp;amp; \\\hline&lt;br /&gt;
     \end{tabular}&lt;br /&gt;
   }&lt;br /&gt;
   { % ... pages&lt;br /&gt;
     \small{&lt;br /&gt;
       \begin{tabular}{lll}&lt;br /&gt;
       [@-- join(' &amp;amp; ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\&lt;br /&gt;
       \textbf{[@-- $date --@]} &amp;amp; \textbf{[@-- $invnum --@]} &amp;amp; \textbf{[@-- $custnum --@]}\\&lt;br /&gt;
       \end{tabular}&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Enable the fancyhdr page style and set the font family.&lt;br /&gt;
&lt;br /&gt;
 \pagestyle{fancy}&lt;br /&gt;
&lt;br /&gt;
 %% Font options are:&lt;br /&gt;
 %%  bch Bitsream Charter&lt;br /&gt;
 %%  put Utopia&lt;br /&gt;
 %%  phv Adobe Helvetica&lt;br /&gt;
 %%  pnc New Century Schoolbook&lt;br /&gt;
 %%  ptm Times&lt;br /&gt;
 %%  pcr Courier&lt;br /&gt;
 &lt;br /&gt;
 \renewcommand{\familydefault}{phv}&lt;br /&gt;
&lt;br /&gt;
=== Line item table macros ===&lt;br /&gt;
&lt;br /&gt;
This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are:&lt;br /&gt;
* Package number&lt;br /&gt;
* Description (6 columns)&lt;br /&gt;
* Price&lt;br /&gt;
&lt;br /&gt;
(The 6 columns are used separately for showing CDRs, if there are any.)&lt;br /&gt;
&lt;br /&gt;
If '''invoice-unitprice''' is on, it's:&lt;br /&gt;
* Package number&lt;br /&gt;
* Description (4 columns)&lt;br /&gt;
* Unit price&lt;br /&gt;
* Quantity&lt;br /&gt;
* Price&lt;br /&gt;
&lt;br /&gt;
According to that setting, create macros for how many columns and how much space to allocate to the description, and for the headers of the Unit Price and Unit Quantity columns.&lt;br /&gt;
&lt;br /&gt;
 % Commands for freeside table header...&lt;br /&gt;
 &lt;br /&gt;
 \newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }&lt;br /&gt;
 \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }&lt;br /&gt;
 \newcommand{\FSunitcolumns}{ [@-- &lt;br /&gt;
   $unitprices&lt;br /&gt;
   ? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &amp;amp;' .&lt;br /&gt;
     '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} &amp;amp; ' &lt;br /&gt;
   : '' --@] }&lt;br /&gt;
&lt;br /&gt;
\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c).&lt;br /&gt;
&lt;br /&gt;
\truncate renders its argument into a space with a maximum width. It will break at word boundaries and insert an ellipsis afterward. Note that if we didn't do this, longtable would either squish the surrounding columns or push them off the page rather than word-wrap the description.&lt;br /&gt;
&lt;br /&gt;
\FSusagehead is the same, but used for usage summary sections that show the number of calls and total minutes. The '''usage_class_summary''' config turns this on.&lt;br /&gt;
&lt;br /&gt;
 \newcommand{\FShead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{\FSdescriptioncolumncount}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \FSunitcolumns&lt;br /&gt;
   \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 \newcommand{\FSusagehead}{&lt;br /&gt;
   \hline&lt;br /&gt;
   \rule{0pt}{2.5ex}&lt;br /&gt;
   \makebox[1.4cm]{} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Calls') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Duration') --@]} &amp;amp;&lt;br /&gt;
   \textbf{~~[@-- emt('Amount') --@]} \\&lt;br /&gt;
   \hline&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Next, the commands to actually display line items.&lt;br /&gt;
&lt;br /&gt;
\FSdesc will be called once for each element in the @detail_items array. This takes five arguments (that's the [5] in the declaration) which are the values to put in the pkgnum, description, unit price, quantity, and price columns. If unit prices are off then arguments 3 and 4 will be empty.&lt;br /&gt;
&lt;br /&gt;
\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font.&lt;br /&gt;
&lt;br /&gt;
\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() ''into LaTeX table rows'' (pre-escaped, delimited with &amp;amp;amp;). So, the macro just skips the first column and outputs its argument.&lt;br /&gt;
&lt;br /&gt;
\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column.&lt;br /&gt;
&lt;br /&gt;
Finally, \FSusagedesc is for usage summary sections, and puts the total calls, duration, and amount in the correct columns.&lt;br /&gt;
&lt;br /&gt;
 % ...description...&lt;br /&gt;
 \newcommand{\FSdesc}[5]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{[@-- $unitprices ? '4' : '6' --@]}{l}{&lt;br /&gt;
     \truncate{\FSdescriptionlength}{\textbf{#2}}&lt;br /&gt;
   } &amp;amp;&lt;br /&gt;
 [@-- $unitprices ? '  \multicolumn{1}{r}{\textbf{#3}} &amp;amp;'.&amp;quot;\n&amp;quot;.&lt;br /&gt;
                    '  \multicolumn{1}{r}{\textbf{#4}} &amp;amp;'.&amp;quot;\n&amp;quot;&lt;br /&gt;
                  : ''&lt;br /&gt;
 --@]&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#5}}\\&lt;br /&gt;
 }&lt;br /&gt;
 % ...extended description...&lt;br /&gt;
 \newcommand{\FSextdesc}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{\small{[[User:Mark|Mark]] ([[User talk:Mark|talk]])#1}}&lt;br /&gt;
   } \\&lt;br /&gt;
 }&lt;br /&gt;
 % ...call detail (multiple columns already)...&lt;br /&gt;
 \newcommand{\FScalldetail}[1]{&lt;br /&gt;
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &amp;amp;&lt;br /&gt;
   [[User:Mark|Mark]] ([[User talk:Mark|talk]])#1&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
 }&lt;br /&gt;
 % ...and total line items (which use the full 12.8cm length, ignoring&lt;br /&gt;
 % unitprice/quantity&lt;br /&gt;
 \newcommand{\FStotaldesc}[2]{&lt;br /&gt;
   &amp;amp; \multicolumn{6}{l}{&lt;br /&gt;
     \truncate{12.8cm}{#1}&lt;br /&gt;
   } &amp;amp; #2\\&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 % ...usage class summary&lt;br /&gt;
 \newcommand{\FSusagedesc}[4]{&lt;br /&gt;
   \multicolumn{1}{c}{\rule{0pt}{2.5ex}} &amp;amp;&lt;br /&gt;
   \multicolumn{4}{l}{\textbf{#1}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#2}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#3}} &amp;amp;&lt;br /&gt;
   \multicolumn{1}{r}{\textbf{#4}}&lt;br /&gt;
   \\&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Main document ===&lt;br /&gt;
&lt;br /&gt;
First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line.&lt;br /&gt;
&lt;br /&gt;
 \begin{document}&lt;br /&gt;
 % Headers and footers defined for the first page&lt;br /&gt;
 \addressinset \rule{0.5cm}{0cm} &lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{7.0cm}&lt;br /&gt;
 \vspace{[@-- defined($addresssep) ? $addresssep : '0.25cm' --@]}&lt;br /&gt;
 \textbf{[@-- $payname --@]}\\&lt;br /&gt;
 \addressline{[@-- $company --@]}&lt;br /&gt;
 \addressline{[@-- $address1 --@]}&lt;br /&gt;
 \addressline{[@-- $address2 --@]}&lt;br /&gt;
 \addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}&lt;br /&gt;
 \addressline{[@-- $country --@]}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
&lt;br /&gt;
Make another minipage for the service address, terms, and PO number. $ship_enable is true if the global '''invoice-ship_address''' or per-customer &amp;quot;invoice_ship_address&amp;quot; flag is on.&lt;br /&gt;
&lt;br /&gt;
The initial \hfill spreads out the two minipages so that they go all the way to the margins. The 'terms' and 'po_line' should probably be in \addressline{} macros but currently aren't.&lt;br /&gt;
&lt;br /&gt;
After that, skip some vertical space. (If you need more vertical space for an invoice format, the 1.5cm here can be cut.)&lt;br /&gt;
&lt;br /&gt;
 \hfill&lt;br /&gt;
 \makebox{&lt;br /&gt;
 \begin{minipage}[t]{6.4cm}&lt;br /&gt;
 [@--&lt;br /&gt;
   if ($ship_enable) {&lt;br /&gt;
     $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_company}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address1}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_address2}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_city, $ship_state~~$ship_zip}&amp;quot;;&lt;br /&gt;
     $OUT .= &amp;quot;\\addressline{$ship_country}&amp;quot;;&lt;br /&gt;
     $OUT .= '~\\\\';&lt;br /&gt;
   }else{&lt;br /&gt;
     $OUT .= '';&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
 \begin{flushright}&lt;br /&gt;
 [@-- $terms ? emt('Terms') . ': ' . emt($terms) : '' --@]\\&lt;br /&gt;
 [@-- $po_line --@]\\&lt;br /&gt;
 \end{flushright}&lt;br /&gt;
 \end{minipage}}&lt;br /&gt;
 \vspace{1.5cm}&lt;br /&gt;
&lt;br /&gt;
Insert the [[Summary]] if there is one. The last thing in the summary template is a page break.&lt;br /&gt;
 %&lt;br /&gt;
 [@-- $summary --@]&lt;br /&gt;
 %&lt;br /&gt;
&lt;br /&gt;
=== Sections ===&lt;br /&gt;
&lt;br /&gt;
Start creating sections, according to the @sections array. Section entries contain the following keys:&lt;br /&gt;
&lt;br /&gt;
* description&lt;br /&gt;
* category (pkg_category.categoryname, for package sections by category)&lt;br /&gt;
* location (hashref of location fields, for package sections by location)&lt;br /&gt;
* pretotal (formatted with \FStotaldesc, before detail lines)&lt;br /&gt;
* subtotal (formatted with \FStotaldesc, at the end)&lt;br /&gt;
* posttotal (line to display after the end of the section; used for &amp;quot;Balance Due&amp;quot; message and a few other things)&lt;br /&gt;
* sort_weight (sort order, but also does complicated things)&lt;br /&gt;
&lt;br /&gt;
and optionally some flags:&lt;br /&gt;
* post_total (put the section at the end of the invoice)&lt;br /&gt;
* summarized (omit the section entirely)&lt;br /&gt;
* usage_section (use \FSusagedesc instead of \FSdesc, see above)&lt;br /&gt;
* no_subtotal (omit the subtotal line)&lt;br /&gt;
&lt;br /&gt;
If '''invoice_sections''' is enabled: the $multisection flag in print_generic() is turned on, and @sections is filled by calling FS::Template_Mixin::_items_sections, which makes a section for each package category. Line items for packages go into the package category's section. '''invoice_sections_method''' can be used to divide sections by location rather than by category. In addition, sections are created for previous invoices, taxes, and &amp;quot;adjustments&amp;quot; (credits and payments to this invoice).&lt;br /&gt;
&lt;br /&gt;
If not: a single section is created, with empty description, containing previous invoices, current charges, and adjustments (after the total). '''previous_balance-section''' will make a previous invoices section anyway.&lt;br /&gt;
&lt;br /&gt;
Additional sections are created in some cases. '''svc_phone_sections''' creates a section for each phone line (see RT#6592; note that this customer is no longer active). '''voip-cust_accountcode_cdr''' creates a section for each accountcode (see RT#12181). '''discount-show_available''' creates a section with information on available discounts. '''usage_class_summary''' creates a section with usage subtotals.&lt;br /&gt;
&lt;br /&gt;
'''finance_pkgclass''' specifies the package class containing &amp;quot;finance charges&amp;quot;, which is handled specially. That class's category name is passed to the template as $finance_section, and the sum of charges in that category is passed as $finance_amount. That amount is broken out from the &amp;quot;Current charges&amp;quot; line in the summary section. On a multisection summary-format invoice, the finance charges will also ''not'' be shown in the body of the invoice. I don't know why.&lt;br /&gt;
&lt;br /&gt;
Loop over all sections (except for the finance charges, maybe). Inside the loop we'll do everything by appending to $OUT.&lt;br /&gt;
&lt;br /&gt;
 \section*{}&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section ( grep { !$summary || $_-&amp;gt;{description} ne $finance_section } @sections ) {&lt;br /&gt;
 &lt;br /&gt;
We're going to do many things if &amp;quot;!$summary&amp;quot;. If there's a summary section, the pretotals and posttotals are redundant so skip them.&lt;br /&gt;
&lt;br /&gt;
    if ($section-&amp;gt;{'pretotal'} &amp;amp;&amp;amp; !$summary) {&lt;br /&gt;
      $OUT .= '\begin{flushright}';&lt;br /&gt;
      $OUT .= '\large\textsc{'. $section-&amp;gt;{'pretotal'}. '}\\\\';&lt;br /&gt;
      $OUT .= '\\end{flushright}';&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
Start a new page if this is a &amp;quot;post_total&amp;quot; section. As far as I know these are used only for usage details. I think the 'summarized' flag is no longer used for anything.&lt;br /&gt;
&lt;br /&gt;
The section heading is going to be a caption placed inside the main table. \captionsetup sets properties for the caption (left justified, bold, small caps, Large size). If this is the first page, also tell longtable (via \LTextracouponspace) to shorten the effective length of the page so that it doesn't overprint the coupon.&lt;br /&gt;
&lt;br /&gt;
    $OUT .= '\pagebreak' if $section-&amp;gt;{'post_total'};&lt;br /&gt;
    unless ($section-&amp;gt;{'summarized'} ) {&lt;br /&gt;
      $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';&lt;br /&gt;
      $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'&lt;br /&gt;
        if $coupon;&lt;br /&gt;
&lt;br /&gt;
Start the longtable that will contain the section's line items. Eight columns, pkgnum (centered), description and optional unitprice/quantity (left), then price (right).&lt;br /&gt;
&lt;br /&gt;
\caption* produces a table caption without prefixing it with &amp;quot;Table 1&amp;quot;. The caption is the section description, or 'Charges' if it's null (except that if packages are sectioned by location, we put one together from the location fields).&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\begin{longtable}{cllllllr}';&lt;br /&gt;
      $OUT .= '\caption*{ ';&lt;br /&gt;
      if ($section-&amp;gt;{'location'}) {&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'label_prefix'}. ': '&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'label_prefix'});&lt;br /&gt;
        $OUT .= $section-&amp;gt;{'location'}{'address1'};&lt;br /&gt;
        $OUT .= ', ' . $section-&amp;gt;{'location'}{'address2'}&lt;br /&gt;
          if length($section-&amp;gt;{'location'}{'address2'});&lt;br /&gt;
        $OUT .= ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'city'} . ', ' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'state'} . '~' .&lt;br /&gt;
                $section-&amp;gt;{'location'}{'zip'};&lt;br /&gt;
      } elsif ( $section-&amp;gt;{'description'} ) {&lt;br /&gt;
        $OUT .= ($section-&amp;gt;{'description'});&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= emt('Charges');&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '}\\\\';&lt;br /&gt;
&lt;br /&gt;
The point of a longtable is that it can span multiple pages, including printing head/foot rows at the top/bottom of each page automatically. The protocol for setting these up is to specify the first head, per-page head, per-page foot, and last foot, then the rest of the table contents. Here, we set the first head to be \FShead (or \FSusagehead). \endfirsthead means we're done specifying it.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;header_generator&amp;quot; and other foo_generators are from an old mechanism that injected callbacks into the template data. This is strongly deprecated.&lt;br /&gt;
&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endfirsthead';&lt;br /&gt;
&lt;br /&gt;
The per-page head: a row with a &amp;quot;Continued&amp;quot; message, then \FShead again. The per-page foot is the same.&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';&lt;br /&gt;
      if ($section-&amp;gt;{header_generator}) {&lt;br /&gt;
        $OUT .= &amp;amp;{$section-&amp;gt;{header_generator}}();&lt;br /&gt;
      } elsif ( $section-&amp;gt;{usage_section} ) {&lt;br /&gt;
        $OUT .= '\FSusagehead';&lt;br /&gt;
      } else {&lt;br /&gt;
        $OUT .= '\FShead';&lt;br /&gt;
      }&lt;br /&gt;
      $OUT .= '\endhead';&lt;br /&gt;
      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';&lt;br /&gt;
      $OUT .= '\endfoot';&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
&lt;br /&gt;
The final foot for the section. For multisection invoices this contains the section subtotal:&lt;br /&gt;
&lt;br /&gt;
      if (scalar(@sections) &amp;gt; 1 and !$section-&amp;gt;{no_subtotal}) {&lt;br /&gt;
        if ($section-&amp;gt;{total_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{total_generator}}($section);&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FStotaldesc{' . $section-&amp;gt;{'description'} . ' Total}' .&lt;br /&gt;
                  '{' . $section-&amp;gt;{'subtotal'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
Single-section invoices use &amp;quot;@total_items&amp;quot; for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot.&lt;br /&gt;
&lt;br /&gt;
      #if ($section == $sections[$#sections]) {&lt;br /&gt;
        foreach my $line (grep {$_-&amp;gt;{section}-&amp;gt;{description} eq $section-&amp;gt;{description}} @total_items) {&lt;br /&gt;
          if ($section-&amp;gt;{total_line_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{total_line_generator}}($line);&lt;br /&gt;
          } else {&lt;br /&gt;
            $OUT .= '\FStotaldesc{' . $line-&amp;gt;{'total_item'} . '}' .&lt;br /&gt;
                    '{' . $line-&amp;gt;{'total_amount'} . '}' . &amp;quot;\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      #}&lt;br /&gt;
&lt;br /&gt;
      $OUT .= '\hline';&lt;br /&gt;
      $OUT .= '\endlastfoot';&lt;br /&gt;
&lt;br /&gt;
Line items are passed in @detail_items. A line item entry can contain:&lt;br /&gt;
&lt;br /&gt;
* section (reference)&lt;br /&gt;
* ref (pkgnum)&lt;br /&gt;
* description&lt;br /&gt;
* ext_description (arrayref of additional lines)&lt;br /&gt;
* quantity&lt;br /&gt;
* amount&lt;br /&gt;
* duration (for usage summary sections)&lt;br /&gt;
* pkgpart&lt;br /&gt;
* unit_amount (unit price)&lt;br /&gt;
* locationnum&lt;br /&gt;
* svc_label (the primary service's label)&lt;br /&gt;
&lt;br /&gt;
Line items are created in several places in Template_Mixin and TemplateItem_Mixin, most importantly _items_cust_bill_pkg.&lt;br /&gt;
&lt;br /&gt;
@detail_items is a single array; we find the lines belonging to this section by checking their 'section' element. If there's only one section, process all the lines.&lt;br /&gt;
&lt;br /&gt;
      my $lastref = 0;&lt;br /&gt;
      foreach my $line (&lt;br /&gt;
        grep { ( scalar( @sections ) &amp;gt; 1 &lt;br /&gt;
               ? $section-&amp;gt;{'description'} eq $_-&amp;gt;{'section'}-&amp;gt;{'description'}&lt;br /&gt;
               : 1&lt;br /&gt;
             ) }&lt;br /&gt;
        @detail_items )&lt;br /&gt;
      {&lt;br /&gt;
        my $ext_description = $line-&amp;gt;{'ext_description'};&lt;br /&gt;
  &lt;br /&gt;
        # Don't break-up small packages.&lt;br /&gt;
        my $rowbreak = @$ext_description &amp;lt; 5 ? '*' : '';&lt;br /&gt;
&lt;br /&gt;
$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum.&lt;br /&gt;
&lt;br /&gt;
If this is a usage summary section, convert the duration to minutes/seconds and call \FSusagedesc, passing the description, quantity (number of calls), duration, and total price.&lt;br /&gt;
&lt;br /&gt;
If it's a normal section, call \FSdesc, passing the description, unit price, quantity, and amount. If unit prices aren't in use, or there isn't one on this line item, then leave unit price and quantity blank.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;$rowbreak&amp;quot; is a flag for whether to allow a page break. In general we want to keep line items and their ext_description lines together, unless there are a lot of description lines. So if there are fewer than 5 of them, we set $rowbreak = '*', which after \FSdesc{} is evaluated, results in the table row delimiter being &amp;quot;\\*&amp;quot;. This tells LaTeX to avoid putting a page break immediately after this row. Probably we should set $rowbreak = '' on the last description line to explicitly allow page breaks between packages.&lt;br /&gt;
&lt;br /&gt;
        $OUT .= &amp;quot;\\hline\n&amp;quot; if (($line-&amp;gt;{'ref'} || 0) ne $lastref);&lt;br /&gt;
        if ($section-&amp;gt;{description_generator}) {&lt;br /&gt;
          $OUT .= &amp;amp;{$section-&amp;gt;{description_generator}}($line);&lt;br /&gt;
        } elsif ($section-&amp;gt;{usage_section}) {&lt;br /&gt;
          my $minutes = sprintf('%d', $line-&amp;gt;{'duration'} / 60);&lt;br /&gt;
          my $seconds = $line-&amp;gt;{'duration'} % 60;&lt;br /&gt;
          $OUT .= '\FSusagedesc&lt;br /&gt;
            {' . $line-&amp;gt;{'description'} . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'quantity'} . '}&lt;br /&gt;
            {' . $minutes . 'm ' . $seconds . 's' . '}&lt;br /&gt;
            {' . $line-&amp;gt;{'amount'} . '}';&lt;br /&gt;
        } else {&lt;br /&gt;
          $OUT .= '\FSdesc'.&lt;br /&gt;
                  '{}'.&lt;br /&gt;
                  '{' . $line-&amp;gt;{'description'} . '}' ;&lt;br /&gt;
          if ( $unitprices and length($line-&amp;gt;{'unit_amount'}) ) {&lt;br /&gt;
            # then show the unit amount and quantity&lt;br /&gt;
            $OUT .= &lt;br /&gt;
                '{\\dollar' . $line-&amp;gt;{'unit_amount'} . '}'.&lt;br /&gt;
                '{'         . $line-&amp;gt;{'quantity'}    . '}';&lt;br /&gt;
          } else {&lt;br /&gt;
            # leave those columns blank&lt;br /&gt;
            $OUT .= '{}{}';&lt;br /&gt;
          }&lt;br /&gt;
          $OUT .= '{\\dollar' . $line-&amp;gt;{'amount'} . &amp;quot;}${rowbreak}\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        $lastref = $line-&amp;gt;{'ref'} || 0;&lt;br /&gt;
&lt;br /&gt;
Output ext_description lines. We assume that any of them that contain an unescaped &amp;quot;&amp;amp;amp;&amp;quot; are call details and should use \FScalldetail. Otherwise they use \FSextdesc.&lt;br /&gt;
&lt;br /&gt;
        foreach my $ext_desc (@$ext_description) {&lt;br /&gt;
          if ($section-&amp;gt;{extended_description_generator}) {&lt;br /&gt;
            $OUT .= &amp;amp;{$section-&amp;gt;{extended_description_generator}}($ext_desc);&lt;br /&gt;
          } elsif ( $ext_desc !~ /[^\\]&amp;amp;/ ) {&lt;br /&gt;
            $OUT .= '\FSextdesc{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          } else { # call detail&lt;br /&gt;
            $OUT .= '\FScalldetail{' . $ext_desc . &amp;quot;}$rowbreak\n&amp;quot;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
End the longtable. If there is a posttotal item in this section (the final Balance Due / Credit Balance message, previous invoice aging, a few other things) then show it right-justified, in the same formatting used for the caption.&lt;br /&gt;
&lt;br /&gt;
       $OUT .= '\end{longtable}';&lt;br /&gt;
     }&lt;br /&gt;
     if ($section-&amp;gt;{'posttotal'}) {&lt;br /&gt;
       $OUT .= '\begin{flushright}';&lt;br /&gt;
       $OUT .= '\normalfont\large\bfseries\textsc{'. $section-&amp;gt;{'posttotal'}. '}\\\\';&lt;br /&gt;
       $OUT .= '\\end{flushright}';&lt;br /&gt;
     }&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
&lt;br /&gt;
For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.)&lt;br /&gt;
&lt;br /&gt;
There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space.&lt;br /&gt;
&lt;br /&gt;
However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the ''strut'' is at the bottom of the page, keeping the coupon space clear.&lt;br /&gt;
&lt;br /&gt;
 --@]&lt;br /&gt;
 \vfill&lt;br /&gt;
 \begin{minipage}[t]{\textwidth}&lt;br /&gt;
   [@-- length($summary)&lt;br /&gt;
          ? ''&lt;br /&gt;
         : ( $smallernotes&lt;br /&gt;
               ? '\scriptsize{ '.$notes.' }'&lt;br /&gt;
               : $notes&lt;br /&gt;
           )&lt;br /&gt;
   --@]&lt;br /&gt;
   [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' : '' --@]&lt;br /&gt;
 \end{minipage}&lt;br /&gt;
 \end{document}&lt;br /&gt;
&lt;br /&gt;
That's the end of the invoice.&lt;br /&gt;
&lt;br /&gt;
== The summary template ==&lt;br /&gt;
&lt;br /&gt;
Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes.&lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{ll}&lt;br /&gt;
 \begin{minipage}{6.4cm}&lt;br /&gt;
 \begin{tabular}{m{0cm}m{6.4cm}}&lt;br /&gt;
 \rule{0cm}{10cm}&amp;amp;\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} &amp;amp;&lt;br /&gt;
&lt;br /&gt;
On the right side, place a 2cm horizontal strut followed by a 12.8cm minipage. This will contain the billing summary.&lt;br /&gt;
&lt;br /&gt;
 \rule{2cm}{0cm}&lt;br /&gt;
 \begin{minipage}{12.8cm}&lt;br /&gt;
&lt;br /&gt;
The billing summary itself is a two-column table, one for labels and one for amounts. The section headings are just labels with no amounts, with lines underneath.&lt;br /&gt;
&lt;br /&gt;
'true_previous_balance' is, as far as we can determine it, the balance that was printed on the customer's previous invoice. It's the sum of all the customer's transactions (invoices, credits, payments, refunds) dated before that invoice, plus the invoice amount.&lt;br /&gt;
&lt;br /&gt;
'balance_adjustments' is the sum of payments and credits received after the previous invoice, but applied to invoices before this current invoice. 'true_previous_balance' - 'balance_adjustments' is the total amount owed on all past invoices.&lt;br /&gt;
&lt;br /&gt;
 \begin{tabular}{lr}&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of Previous Balance and Payments}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{Previous Balance}&amp;amp;\textbf{\dollar[@-- $true_previous_balance --@]}\\&lt;br /&gt;
 \textbf{Payments}&amp;amp;\textbf{\dollar[@-- $balance_adjustments --@]}\\&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Balance Outstanding}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Summary of New Charges}} &amp;amp; \\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
&lt;br /&gt;
@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. This is followed with \cline{2-2}, which puts a horizontal line under the second column only, and then a row for the total of new charges.&lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   foreach my $section (@summary_subtotals) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'} ? $section-&amp;gt;{'description'} : 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
   $OUT .= '\cline{2-2}';&lt;br /&gt;
 --@]&lt;br /&gt;
 \textbf{New Charges Total}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \textbf{\underline{Invoice Summary}} &amp;amp; \\&lt;br /&gt;
 &amp;amp; \\&lt;br /&gt;
 \textbf{Previous Past Due Charges}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\&lt;br /&gt;
 \textbf{Finance charges on overdue amount}&amp;amp;\textbf{\dollar[@-- $finance_amount --@]}\\&lt;br /&gt;
 \textbf{New Charges}&amp;amp;\textbf{\dollar[@-- $current_less_finance --@]}\\&lt;br /&gt;
&lt;br /&gt;
Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the ''current'' invoice.&lt;br /&gt;
&lt;br /&gt;
 [@--&lt;br /&gt;
   #false laziness w/invoice_htmlsummary and above&lt;br /&gt;
   foreach my $section ( grep $_-&amp;gt;{adjust_section}, @sections ) {&lt;br /&gt;
     $OUT .= '\textbf{'. ($section-&amp;gt;{'description'} ? $section-&amp;gt;{'description'} : 'Charges' ). '}';&lt;br /&gt;
     $OUT .= '&amp;amp;\textbf{'. $section-&amp;gt;{'subtotal'}. '}\\\\';&lt;br /&gt;
   }&lt;br /&gt;
 --@]&lt;br /&gt;
&lt;br /&gt;
Finally, the customer's balance including the current invoice, and then close the table and minipage, and force a page break so the line items can start on a new page.&lt;br /&gt;
&lt;br /&gt;
 \cline{2-2}&lt;br /&gt;
 \textbf{Total Amount Due}&amp;amp;\textbf{\dollar[@-- sprintf('%.2f', $balance) --@]}\\&lt;br /&gt;
 &amp;amp;\\&lt;br /&gt;
 \hline&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \end{minipage} \\&lt;br /&gt;
 \end{tabular}&lt;br /&gt;
 \newpage&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Invoice modes ==&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:InstallingOnDebian8&amp;diff=9589</id>
		<title>Freeside:4:Documentation:InstallingOnDebian8</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:InstallingOnDebian8&amp;diff=9589"/>
				<updated>2016-09-14T20:05:13Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Change options for createuser for deb8&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Configure package repositories =&lt;br /&gt;
* Add the following apt sources to &amp;lt;code&amp;gt;/etc/apt/sources.list&amp;lt;/code&amp;gt; (for Debian 8.x &amp;quot;jessie&amp;quot;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
deb http://freeside.biz/~ivan/freeside-jessie/ ./&lt;br /&gt;
deb http://freeside.biz/~jeremyd/freeside4-jessie-stable/ ./&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Run &amp;lt;code&amp;gt;aptitude update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Install  =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
aptitude install freeside freeside-lib freeside-webui&lt;br /&gt;
apt-mark hold freeside*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Database setup =&lt;br /&gt;
&lt;br /&gt;
== Database User ==&lt;br /&gt;
* Allow the freeside user full access to the freeside database. &lt;br /&gt;
with Postgresql:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[ as postgres/pgsql user ]&lt;br /&gt;
$ createuser -P -d freeside &lt;br /&gt;
Enter password for new role: &lt;br /&gt;
Enter it again: &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
or with MySQL:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ mysqladmin -u root password 'set_a_root_database_password'&lt;br /&gt;
$ mysql -u root -p&lt;br /&gt;
mysql&amp;gt; GRANT SELECT,INSERT,UPDATE,DELETE,INDEX,ALTER,CREATE,DROP on freeside.* TO freeside@localhost IDENTIFIED BY 'set_a_freeside_database_password';&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Database Access ==&lt;br /&gt;
* Configure /usr/local/etc/freeside/secrets if necessary.  This file contains three lines: DBI datasource, username and password&lt;br /&gt;
** See the DBI manpage and the manpage for your DBD for the exact syntax of your DBI data source. &lt;br /&gt;
&lt;br /&gt;
== Database ==&lt;br /&gt;
* Add the freeside database to your database engine: &lt;br /&gt;
with Postgres: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ su freeside&lt;br /&gt;
$ createdb -E UTF8 freeside&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
or with MySQL: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ mysqladmin -u freeside -p create freeside &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Bootstrap Freeside =&lt;br /&gt;
&lt;br /&gt;
== Freeside database ==&lt;br /&gt;
&lt;br /&gt;
* As the freeside UNIX user, run &amp;lt;code&amp;gt;freeside-setup -d your.domain.name&amp;lt;/code&amp;gt; to create the database tables and initial data.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ su freeside&lt;br /&gt;
$ freeside-setup -d example.com&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== RT database ==&lt;br /&gt;
&lt;br /&gt;
* As the freeside UNIX user, run:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ su freeside&lt;br /&gt;
$ /opt/rt3/sbin/rt-setup-database --action schema&lt;br /&gt;
$ /opt/rt3/sbin/rt-setup-database --action coredata&lt;br /&gt;
$ /opt/rt3/sbin/rt-setup-database --action insert --datafile /opt/rt3/etc/initialdata&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== System users ==&lt;br /&gt;
* Create the Freeside system users: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ su freeside&lt;br /&gt;
$ freeside-adduser -g 1 fs_queue&lt;br /&gt;
$ freeside-adduser -g 1 fs_daily&lt;br /&gt;
$ freeside-adduser -g 1 fs_selfservice&lt;br /&gt;
$ freeside-adduser -g 1 fs_api&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Employees ==&lt;br /&gt;
* Create one or more Freeside users (your internal sales/tech folks, not customer accounts):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ su freeside&lt;br /&gt;
$ freeside-adduser -g 1 username&lt;br /&gt;
$ htpasswd /usr/local/etc/freeside/htpasswd username&lt;br /&gt;
Password: &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Additional users can be added using the same command or from the web interface.&lt;br /&gt;
&lt;br /&gt;
= Restart freeside =&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;/etc/init.d/freeside restart&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Apache &amp;amp; Web GUI =&lt;br /&gt;
* Edit /etc/apache2/envvars and set APACHE_RUN_USER and APACHE_RUN_GROUP to &amp;lt;code&amp;gt;freeside&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;a2enconf freeside-base2&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;a2enconf freeside-rt&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;a2dismod mpm_event&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;a2enmod mpm_prefork&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;chown freeside /var/lock/apache2&amp;lt;/code&amp;gt;&lt;br /&gt;
* Restart Apache&lt;br /&gt;
* The web interface will be available at /freeside&lt;br /&gt;
&lt;br /&gt;
= Next steps =&lt;br /&gt;
* Log into the web interface using the username and password you entered above.&lt;br /&gt;
* Proceed to the initial [[Freeside:3:Documentation:Administration|administration]] of your installation.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:Configuration:invoice-all_pkg_addresses&amp;diff=9577</id>
		<title>Freeside:Configuration:invoice-all pkg addresses</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:Configuration:invoice-all_pkg_addresses&amp;diff=9577"/>
				<updated>2016-07-13T00:12:15Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;Category:Freeside_Configuration_Settings Category:Invoice_Layout ;invoice-all_pkg_addresses :Show all package addresses on invoices, even the default.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Freeside_Configuration_Settings]]&lt;br /&gt;
[[Category:Invoice_Layout]]&lt;br /&gt;
;invoice-all_pkg_addresses&lt;br /&gt;
:Show all package addresses on invoices, even the default.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Administration:invoice_layout&amp;diff=9576</id>
		<title>Freeside:3:Documentation:Administration:invoice layout</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Administration:invoice_layout&amp;diff=9576"/>
				<updated>2016-07-13T00:10:54Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: /* Boolean Rendering Variables */ invoice-all_pkg_addresses&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;===Invoice Layout and Content===&lt;br /&gt;
====Basic Invoice Styles====&lt;br /&gt;
Several variables make significant changes to the appearance of invoices.&lt;br /&gt;
&lt;br /&gt;
{{Freeside:Configuration:invoice_sections}}&lt;br /&gt;
{{Freeside:Configuration:invoice_usesummary}}&lt;br /&gt;
{{Freeside:Configuration:usage_class_as_a_section}}&lt;br /&gt;
{{Freeside:Configuration:svc_phone_sections}}&lt;br /&gt;
&lt;br /&gt;
====Invoice Templates====&lt;br /&gt;
{{Freeside:3:Documentation:Administration:invoice_templates|format=latex}}&lt;br /&gt;
&lt;br /&gt;
{{Freeside:3:Documentation:Administration:VOIP plan invoice layout variables}}&lt;br /&gt;
&lt;br /&gt;
==== Other Variables Changing Invoice Layout at Billing Time====&lt;br /&gt;
{{Freeside:Configuration:separate_usage}}&lt;br /&gt;
&lt;br /&gt;
==== Variables Neither Perfectly Rendering nor Perfectly Generational====&lt;br /&gt;
{{Freeside:Configuration:date_format}}&lt;br /&gt;
{{Freeside:Configuration:money_char}}&lt;br /&gt;
{{Freeside:Configuration:invoice_default_terms}}&lt;br /&gt;
&lt;br /&gt;
====Boolean Rendering Variables====&lt;br /&gt;
{{Freeside:Configuration:invoice_show_prior_due_date}}&lt;br /&gt;
{{Freeside:Configuration:invoice_include_aging}}&lt;br /&gt;
{{Freeside:Configuration:invoice_smallernotes}}&lt;br /&gt;
{{Freeside:Configuration:invoice_smallerfooter}}&lt;br /&gt;
{{Freeside:Configuration:invoice-ship_address}}&lt;br /&gt;
{{Freeside:Configuration:invoice-all_pkg_addresses}}&lt;br /&gt;
{{Freeside:Configuration:disable_line_item_date_ranges}}&lt;br /&gt;
{{Freeside:Configuration:disable_previous_balance}}&lt;br /&gt;
{{Freeside:Configuration:previous_balance-summary_only}}&lt;br /&gt;
{{Freeside:Configuration:balance_due_below_line}}&lt;br /&gt;
{{Freeside:Configuration:invoice-unitprice}}&lt;br /&gt;
{{Freeside:Configuration:previous_balance-section}}&lt;br /&gt;
&lt;br /&gt;
====Other Rendering Variables====&lt;br /&gt;
{{Freeside:Configuration:invoice_latextopmargin}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexheadsep}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexaddresssep}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latextextheight}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexextracouponspace}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexcouponfootsep}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexcouponamountenclosedsep}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexcoupontoaddresssep}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexverticalreturnaddress}}&lt;br /&gt;
{{Freeside:Configuration:invoice_latexcouponaddcompanytoaddress}}&lt;br /&gt;
{{Freeside:Configuration:previous_balance-exclude_from_total}}&lt;br /&gt;
{{Freeside:Configuration:company_name}}&lt;br /&gt;
{{Freeside:Configuration:company_address}}&lt;br /&gt;
{{Freeside:Configuration:finance_pkgclass}}&lt;br /&gt;
{{Freeside:Configuration:cust_bill-max_same_services}}&lt;br /&gt;
{{Freeside:Configuration:cust_bill-consolidate_services}}&lt;br /&gt;
&lt;br /&gt;
====Miscellaneous Variables Impacting Customer Perception of Invoices====&lt;br /&gt;
&lt;br /&gt;
These variables do not change the invoice per se, but do change what is emailed to a customer as an invoice.&lt;br /&gt;
{{Freeside:Configuration:invoice_email_pdf}}&lt;br /&gt;
{{Freeside:Configuration:invoice_email_pdf_note}}&lt;br /&gt;
{{Freeside:Configuration:voip-cust_email_csv_cdr}}&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9575</id>
		<title>Freeside:4:Documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9575"/>
				<updated>2016-06-28T20:42:56Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Reverted edits by Mark (talk) to last revision by Ivan&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Note =&lt;br /&gt;
&lt;br /&gt;
Documentation specific to git master / 4.x.  For now, refer to this as an add-on to the 3.x documentation.&lt;br /&gt;
&lt;br /&gt;
= Installation and upgrades =&lt;br /&gt;
&lt;br /&gt;
The minimum Perl version is now 5.10.&lt;br /&gt;
&lt;br /&gt;
== Backend Installation ==&lt;br /&gt;
&lt;br /&gt;
* New installation&lt;br /&gt;
* Integrated RT installation&lt;br /&gt;
* OS-specific installation guides (source)&lt;br /&gt;
** [[Freeside:4:Documentation:InstallingOnDebian8]]&lt;br /&gt;
&lt;br /&gt;
== Upgrading ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Upgrading|Upgrading from 3.3 or later to 4.x]]&lt;br /&gt;
&lt;br /&gt;
= Administrator =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Multi-currency|Multi-currency]]&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Fees|Automated fees]]&lt;br /&gt;
* [[Freeside:4:Documentation:Cacti|Cacti Integration]]&lt;br /&gt;
* [[Freeside:4:Documentation:MagicMail|MagicMail Integration]]&lt;br /&gt;
&lt;br /&gt;
= Developer =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Developer:Authentication_Plugins|Authentication Plugins]]&lt;br /&gt;
* [[Freeside:4:Documentation:TaxEngine|Tax Engines]]&lt;br /&gt;
&lt;br /&gt;
= Changelog =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4.0:Changelog|4.0 Changelog]]&lt;br /&gt;
&lt;br /&gt;
= New feature documentation =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Appointments]]&lt;br /&gt;
* Emails triggered by system log events can be set up at Configuration -&amp;gt; Miscellaneous -&amp;gt; System log emails&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9574</id>
		<title>Freeside:4:Documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9574"/>
				<updated>2016-06-28T19:17:36Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Note =&lt;br /&gt;
&lt;br /&gt;
Documentation specific to git master / 4.x.  For now, refer to this as an add-on to the 3.x documentation.&lt;br /&gt;
&lt;br /&gt;
= Installation and upgrades =&lt;br /&gt;
&lt;br /&gt;
The minimum Perl version is now 5.10.&lt;br /&gt;
&lt;br /&gt;
== Backend Installation ==&lt;br /&gt;
&lt;br /&gt;
* New installation&lt;br /&gt;
* Integrated RT installation&lt;br /&gt;
* OS-specific installation guides (source)&lt;br /&gt;
** [[Freeside:4:Documentation:InstallingOnDebian8]]&lt;br /&gt;
&lt;br /&gt;
== Upgrading ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Upgrading|Upgrading from 3.3 or later to 4.x]]&lt;br /&gt;
&lt;br /&gt;
= Administrator =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Multi-currency|Multi-currency]]&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Fees|Automated fees]]&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:SureTax|SureTax]]&lt;br /&gt;
* [[Freeside:4:Documentation:Cacti|Cacti Integration]]&lt;br /&gt;
* [[Freeside:4:Documentation:MagicMail|MagicMail Integration]]&lt;br /&gt;
&lt;br /&gt;
= Developer =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Developer:Authentication_Plugins|Authentication Plugins]]&lt;br /&gt;
* [[Freeside:4:Documentation:TaxEngine|Tax Engines]]&lt;br /&gt;
&lt;br /&gt;
= Changelog =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4.0:Changelog|4.0 Changelog]]&lt;br /&gt;
&lt;br /&gt;
= New feature documentation =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Appointments]]&lt;br /&gt;
* Emails triggered by system log events can be set up at Configuration -&amp;gt; Miscellaneous -&amp;gt; System log emails&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9573</id>
		<title>Freeside:4:Documentation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation&amp;diff=9573"/>
				<updated>2016-06-28T19:17:20Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Note =&lt;br /&gt;
&lt;br /&gt;
Documentation specific to git master / 4.x.  For now, refer to this as an add-on to the 3.x documentation.&lt;br /&gt;
&lt;br /&gt;
= Installation and upgrades =&lt;br /&gt;
&lt;br /&gt;
The minimum Perl version is now 5.10.&lt;br /&gt;
&lt;br /&gt;
== Backend Installation ==&lt;br /&gt;
&lt;br /&gt;
* New installation&lt;br /&gt;
* Integrated RT installation&lt;br /&gt;
* OS-specific installation guides (source)&lt;br /&gt;
** [[Freeside:4:Documentation:InstallingOnDebian8]]&lt;br /&gt;
&lt;br /&gt;
== Upgrading ==&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Upgrading|Upgrading from 3.3 or later to 4.x]]&lt;br /&gt;
&lt;br /&gt;
= Administrator =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Multi-currency|Multi-currency]]&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Fees|Automated fees]]&lt;br /&gt;
* [[Freeside:4:Documentation:Administrator:Fees|SureTax]]&lt;br /&gt;
* [[Freeside:4:Documentation:Cacti|Cacti Integration]]&lt;br /&gt;
* [[Freeside:4:Documentation:MagicMail|MagicMail Integration]]&lt;br /&gt;
&lt;br /&gt;
= Developer =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Developer:Authentication_Plugins|Authentication Plugins]]&lt;br /&gt;
* [[Freeside:4:Documentation:TaxEngine|Tax Engines]]&lt;br /&gt;
&lt;br /&gt;
= Changelog =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4.0:Changelog|4.0 Changelog]]&lt;br /&gt;
&lt;br /&gt;
= New feature documentation =&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:4:Documentation:Appointments]]&lt;br /&gt;
* Emails triggered by system log events can be set up at Configuration -&amp;gt; Miscellaneous -&amp;gt; System log emails&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:User&amp;diff=9561</id>
		<title>Freeside:3:Documentation:User</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:User&amp;diff=9561"/>
				<updated>2016-05-24T20:29:57Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: /* Tools */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
== About ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_1.html ?&lt;br /&gt;
&lt;br /&gt;
AGPL now&lt;br /&gt;
&lt;br /&gt;
== Navigation and Preferences ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_2.html&lt;br /&gt;
&lt;br /&gt;
== Customers, Packages and Services ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_3.html&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_4.html (simple case: single package and service)&lt;br /&gt;
&lt;br /&gt;
= Prospects =&lt;br /&gt;
&lt;br /&gt;
== Adding prospcts ==&lt;br /&gt;
&lt;br /&gt;
== Searching for prospects ==&lt;br /&gt;
&lt;br /&gt;
== Prospect view ==&lt;br /&gt;
&lt;br /&gt;
=== Qualifications ===&lt;br /&gt;
&lt;br /&gt;
If you have any qualification-capable services configured, you may perform qualifications using the &amp;quot;New Qualification&amp;quot; link.  Previously-performed qualifications may be viewed from &amp;quot;View Qualifications&amp;quot;.  See [[#Qualifications 2|Package Qualifications]] for details.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Customers =&lt;br /&gt;
&lt;br /&gt;
== Adding customers ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_5.html&lt;br /&gt;
&lt;br /&gt;
== Searching for customers ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customer view ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_6.html&lt;br /&gt;
&lt;br /&gt;
=== Actions ===&lt;br /&gt;
&lt;br /&gt;
==== Edit customer ====&lt;br /&gt;
==== Cancel customer ====&lt;br /&gt;
==== Refer customer ====&lt;br /&gt;
==== View customer's referrals ====&lt;br /&gt;
==== Bill now ====&lt;br /&gt;
&lt;br /&gt;
=== Comments / Notes ===&lt;br /&gt;
&lt;br /&gt;
==== Comments ====&lt;br /&gt;
&lt;br /&gt;
==== Notes ====&lt;br /&gt;
&lt;br /&gt;
=== Tickets ===&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_7.html&lt;br /&gt;
&lt;br /&gt;
=== Packages ===&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_11.html&lt;br /&gt;
&lt;br /&gt;
==== Package Dates ====&lt;br /&gt;
&lt;br /&gt;
There are several dates which can be set on a package for a wide range of functionality&lt;br /&gt;
&lt;br /&gt;
* Start date - Delay the package's first billing until a date in the future.  Setting this in the past will have no effect and the package will bill immediately.&lt;br /&gt;
* Setup date - The first day the package bills.  This also controls the billing of setup fees.  Once a setup date is established on a package the setup fee will not bill&lt;br /&gt;
&lt;br /&gt;
* Last bill date - The last date the package billed&lt;br /&gt;
&lt;br /&gt;
* Next bill date - The next date the package will bill.&lt;br /&gt;
&lt;br /&gt;
* Contract end date - Notes the end date of the customers current contract.  Does not have any billing implications unless you setup billing events to suspend packages which have a lapsed contract end date.  This date can also be used in the advanced package reports for auditing purposes.&lt;br /&gt;
&lt;br /&gt;
It is not advised to manually edit last or next bill dates as editing these incorrectly can have undesired results.&lt;br /&gt;
&lt;br /&gt;
'''NOTE: Package date editing will deprecated in 4.x in favor of specific work flow functionality.'''&lt;br /&gt;
&lt;br /&gt;
==== Qualifications ====&lt;br /&gt;
&lt;br /&gt;
If you have any qualification-capable services configured, you may perform qualifications using the &amp;quot;New Qualification&amp;quot; link under Packages, and then choosing the &amp;quot;Qualify using&amp;quot; field appropriately and filling out the form.&lt;br /&gt;
&lt;br /&gt;
Previously-performed qualifications may be viewed from &amp;quot;View Qualifications&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Notes specific to New Qualification for Ikano:&lt;br /&gt;
# An address is always required&lt;br /&gt;
# Location Type may be only one of the fixed Ikano values - see their documentation&lt;br /&gt;
# Location Kind must always be chosen&lt;br /&gt;
# Dry loops - always leave &amp;quot;Service Telephone Number&amp;quot; empty&lt;br /&gt;
# Line-share (non dry loops) - always fill in &amp;quot;Service Telephone Number&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Notes specific to qualification results / viewing previous qualifications for Ikano:&lt;br /&gt;
# The view qualification page will show all possible packages in Freeside which you can order, based on the customer data which was qualified&lt;br /&gt;
# Click on the package to order&lt;br /&gt;
&lt;br /&gt;
==== Services ====&lt;br /&gt;
&lt;br /&gt;
===== Accounts =====&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_12.html&lt;br /&gt;
&lt;br /&gt;
===== Domains =====&lt;br /&gt;
===== Mail Forwards =====&lt;br /&gt;
===== Virtual hosting =====&lt;br /&gt;
===== Broadband =====&lt;br /&gt;
===== Phone numbers =====&lt;br /&gt;
===== External =====&lt;br /&gt;
===== DSL =====&lt;br /&gt;
&lt;br /&gt;
Ikano-specific notes:&lt;br /&gt;
* New Orders&lt;br /&gt;
# Perform a qualification (see Qualifications above)&lt;br /&gt;
# Order one of the qualifying packages shown on the qualification result and set the start date and location on this package appropriately&lt;br /&gt;
# For dry loops, leave the Service Telephone Number blank and choose the &amp;quot;Standalone&amp;quot; Loop Type&lt;br /&gt;
# For line-share (non dry-loops), always fill in the Service Telephone Number and choose the &amp;quot;Line-share&amp;quot; Loop Type&lt;br /&gt;
* Viewing DSL Orders - click on the service to view all order data, including notes placed on the order by Ikano or you via their web portal&lt;br /&gt;
* Canceling DSL Orders&lt;br /&gt;
** A NEW order in NEW or PENDING statuses may be canceled immediately - do a normal &amp;quot;Cancel now&amp;quot; on the package&lt;br /&gt;
** A NEW order in COMPLETED status may be canceled by expiring the package - do a normal &amp;quot;Cancel later&amp;quot; on the package with a cancel date at least 48 hours in future&lt;br /&gt;
* Changing PPPoE password - edit the service and change the password&lt;br /&gt;
* Suspending and unsuspending - do a normal Suspend Now or Unsuspend on the package&lt;br /&gt;
* Processes not supported currently (use the Ikano web interface for now):&lt;br /&gt;
** Canceling NEW orders in any status other than NEW, PENDING, or COMPLETED &lt;br /&gt;
** Anything involving CHANGE orders - e.g. changing the package/service, canceling a change/move, performing a move&lt;br /&gt;
** Aborting cancellations (unexpiring packages) while a CANCEL order is in NEW or PENDING status&lt;br /&gt;
** Syncing an order's disconnect or activation date to the package dates&lt;br /&gt;
* Due to Ikano's API, it will not be possible to place notes on an order from Freeside, so this cannot be implemented&lt;br /&gt;
&lt;br /&gt;
=== Payment history and actions === &lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_13.html&lt;br /&gt;
&lt;br /&gt;
==== Invoices ====&lt;br /&gt;
&lt;br /&gt;
Invoices are generated by the system.  The charges on each invoice reflect the setup/one-time, recurring and usage charges of that customer's packages.  Invoices increase the customer's balance.&lt;br /&gt;
&lt;br /&gt;
To add new packages or charges to a customer, use the &amp;quot;Order new package&amp;quot; or &amp;quot;One-time charge&amp;quot; links in the Packages section of the Customer View page.&lt;br /&gt;
&lt;br /&gt;
To generate a pending invoice for a customer, use the &amp;quot;Bill now&amp;quot; link at the top of the Customer View page.&lt;br /&gt;
&lt;br /&gt;
==== Payments ====&lt;br /&gt;
&lt;br /&gt;
Payments are money the customer pays.  Payments decrease the customer's balance.&lt;br /&gt;
&lt;br /&gt;
===== Check and Cash payments =====&lt;br /&gt;
&lt;br /&gt;
* To post a check or cash payment, use the &amp;quot;Enter check payment&amp;quot; or &amp;quot;Enter cash payment&amp;quot; links at the top of the Payment History section of the customer view page.&lt;br /&gt;
** Enter the amount of the payment.&lt;br /&gt;
** For check payments, enter the check number.&lt;br /&gt;
** Leave &amp;quot;Auto-apply to invoices&amp;quot; set to &amp;quot;yes&amp;quot; if you would like the system to apply the payment to any open invoices (oldest first).  Or change &amp;quot;Auto-apply to invoices&amp;quot; to &amp;quot;no&amp;quot; if you would like to apply the payment to invoice(s) (or refund(s)) manually.&lt;br /&gt;
&lt;br /&gt;
* Multiple check payments can also be entered in a batch under Tools -&amp;gt; Quick Payment Entry.&lt;br /&gt;
&lt;br /&gt;
===== Credit card and electronic check (ACH) payments =====&lt;br /&gt;
&lt;br /&gt;
* Credit card or electronic check payments will be initiated by the system for customers with Billing Type &amp;quot;Credit card (automatic)&amp;quot; or &amp;quot;Electronic Check (automatic)&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
* Payments can be initiated manually by using the &amp;quot;Process credit card payment&amp;quot; or &amp;quot;Process electronic check (ACH) payment&amp;quot; links, in the Payment History section of the customer view page.&lt;br /&gt;
** Enter the amount to process (defaults to the customer's current outstanding balance).&lt;br /&gt;
** For credit card payments, enter the card number, expiration date, name on card and billing address.  If the customer has a card on-file, the masked card number (&amp;quot;4111xxxxxxxx1111&amp;quot;) and other information can be left as-is to charge the on-file card.&lt;br /&gt;
** For electronic check payments, enter the account number, account type, ABA or routing number, and bank name (other information such as bank state, social security number, or driver's license number may also be required by some payment gateways).  If the customer has electronic checking information on-file, the masked account number and other information can be left as-is to charge the on-file bank account.&lt;br /&gt;
** Check &amp;quot;Remember this information&amp;quot; to save the payment information on-file.&lt;br /&gt;
** Also check &amp;quot;Charge future payments to this credit card/electronic check automatically&amp;quot; to charge the customer automatically in the future.&lt;br /&gt;
&lt;br /&gt;
* Customers may use the self-service interface to pay by credit card or electronic check themselves.&lt;br /&gt;
&lt;br /&gt;
===== Payment application =====&lt;br /&gt;
&lt;br /&gt;
* Payments are applied to a specific invoice (or split between multiple invoices).&lt;br /&gt;
* The &amp;quot;(apply)&amp;quot; and &amp;quot;(unapply)&amp;quot; links next to specific payment (in the Payment History section of the customer view page) can be used to change the application of payments to specific invoice(s) and/or refunds(s).&lt;br /&gt;
&lt;br /&gt;
==== Credits ====&lt;br /&gt;
&lt;br /&gt;
Credits are adjustments to the amount the customer owes.  Credits decrease the customer's balance.&lt;br /&gt;
&lt;br /&gt;
* To post a credit, use the &amp;quot;Enter credit&amp;quot; link at the top of the Payment History section of the customer view page.&lt;br /&gt;
** Enter the amount to credit.&lt;br /&gt;
** Select the reason for the credit or enter a new reason.&lt;br /&gt;
** Leave &amp;quot;Auto-apply to invoices&amp;quot; set to &amp;quot;yes&amp;quot; if you would like the system to apply the credit to any open invoices (oldest first).  Or change &amp;quot;Auto-apply to invoices&amp;quot; to &amp;quot;no&amp;quot; if you would like to apply the credit to invoice(s) (or refund(s)) manually.&lt;br /&gt;
&lt;br /&gt;
* Like payments, credits can be applied to an invoice (or split between multiple invoices).  For example, a credit to reverse an incorrect invoice or write off an invoice as bad debt could be applied to that specific invoice.&lt;br /&gt;
* Credits can also be applied to refunds to indicate that the credit was refunded to the customer instead.&lt;br /&gt;
* The &amp;quot;(apply)&amp;quot; and &amp;quot;(unapply)&amp;quot; links next to specific credits (in the Payment History section of the customer view page) can be used to change the application of credits to specific invoice(s) and/or refunds(s).&lt;br /&gt;
&lt;br /&gt;
==== Refunds ====&lt;br /&gt;
&lt;br /&gt;
Refunds are money paid to the customer.  Refunds increase the customer's balance.&lt;br /&gt;
&lt;br /&gt;
===== Check and Cash refunds =====&lt;br /&gt;
&lt;br /&gt;
* Posting refunds&lt;br /&gt;
* Applying and unapplying refunds.&lt;br /&gt;
&lt;br /&gt;
===== Credit card and electronic check (ACH) refunds =====&lt;br /&gt;
&lt;br /&gt;
* To refund a specific credit card or electronic check (ACH) payment back to the customer, use the &amp;quot;(refund)&amp;quot; link next to that specific payment (in the Payment History section of the customer view page).   This will process a refund for the amount of the payment with the payment gateway.&lt;br /&gt;
&lt;br /&gt;
= Ticketing =&lt;br /&gt;
&lt;br /&gt;
Link to RT documentation&lt;br /&gt;
&lt;br /&gt;
== New ticket ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_8.html&lt;br /&gt;
&lt;br /&gt;
== Ticketing Main ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_9.html&lt;br /&gt;
&lt;br /&gt;
== Ticket View ==&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_10.html&lt;br /&gt;
&lt;br /&gt;
= Searching and Reporting =&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_14.html&lt;br /&gt;
&lt;br /&gt;
== Customers ==&lt;br /&gt;
&lt;br /&gt;
* To search for a customer, enter the customer number, name, company name or contact phone number into the &amp;quot;Search customers&amp;quot; box at the top of each page.  Name and company are &amp;quot;fuzzy&amp;quot; searches that will attempt to find a close match if no exact match is found.&lt;br /&gt;
* Other customer browsing and reporting is available from Reports -&amp;gt; Customers&lt;br /&gt;
* For advanced reporting with specific criteria, use the &amp;quot;Advanced&amp;quot; link next to the &amp;quot;Search customers&amp;quot; box at the top of each page, or go to Reports -&amp;gt; Customers -&amp;gt; Advanced customer reports&lt;br /&gt;
&lt;br /&gt;
* To search for a customer by invoice number or service information (such as username, email address, domain or service phone number), use the invoice or service search, then click on the customer or &amp;quot;View this customer&amp;quot; link.&lt;br /&gt;
&lt;br /&gt;
== Invoices ==&lt;br /&gt;
&lt;br /&gt;
* To search for an invoice, enter the invoice number into the &amp;quot;Search customers&amp;quot; box at the top of each page.&lt;br /&gt;
* Other invoice browsing and reporting is available from Reports -&amp;gt; Invoices&lt;br /&gt;
* For advanced reporting with specific criteria, use the &amp;quot;Advanced&amp;quot; link next to the &amp;quot;Search invoice&amp;quot; box at the top of each page, or go to Reports -&amp;gt; Invoices -&amp;gt; Advanced invoice reports&lt;br /&gt;
&lt;br /&gt;
== Packages ==&lt;br /&gt;
&lt;br /&gt;
* Package browsing and reporting is available from Reports -&amp;gt; Packages&lt;br /&gt;
* For advanced reporting with specific criteria, go to Reports -&amp;gt; Packages -&amp;gt; Advanced package reports&lt;br /&gt;
&lt;br /&gt;
=== FCC Form 477 ===&lt;br /&gt;
&lt;br /&gt;
* See [[Freeside:3:Documentation:User:Form_477]].&lt;br /&gt;
&lt;br /&gt;
== Services ==&lt;br /&gt;
&lt;br /&gt;
* To search for a service such as username, email address, IP address, MAC address, domain or service phone number,  enter the search data into the &amp;quot;Search servcies&amp;quot; box at the top of each page.&lt;br /&gt;
* Other service browsing and reporting is available from Reports -&amp;gt; Services&lt;br /&gt;
&lt;br /&gt;
=== Accounts ===&lt;br /&gt;
=== Domains ===&lt;br /&gt;
=== Mail Forwards ===&lt;br /&gt;
=== Virtual hosting ===&lt;br /&gt;
=== Broadband ===&lt;br /&gt;
=== Phone numbers ===&lt;br /&gt;
=== External ===&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
&lt;br /&gt;
== Tickets ==&lt;br /&gt;
&lt;br /&gt;
* To search for a ticket, enter the ticket number, subject, or email address into the &amp;quot;Search tickets&amp;quot; box at the top of each page.&lt;br /&gt;
* A fulltext ticket search can also be performed by entering &amp;quot;fulltext:searchstring&amp;quot; where searchstring is the string to search for.&lt;br /&gt;
* For advanced reporting with specific criteria, use the &amp;quot;Advanced&amp;quot; link next to the &amp;quot;Search tickets&amp;quot; box at the top of each page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Financial Reports ==&lt;br /&gt;
&lt;br /&gt;
=== Sales, Credits and Receipts ===&lt;br /&gt;
&lt;br /&gt;
=== Sales Report ===&lt;br /&gt;
&lt;br /&gt;
=== Credit Report ===&lt;br /&gt;
&lt;br /&gt;
=== Payment Report ===&lt;br /&gt;
&lt;br /&gt;
=== Payment Batch Report ===&lt;br /&gt;
&lt;br /&gt;
=== A/R Aging ===&lt;br /&gt;
&lt;br /&gt;
Go to Reports -&amp;gt; Financial -&amp;gt; A/R aging to pull up an aging report&lt;br /&gt;
&lt;br /&gt;
* Select &amp;quot;All customers&amp;quot; to pull up a report including all customers.&lt;br /&gt;
* Select &amp;quot;Customers with a balance&amp;quot; (the default) to pull up a report only including customers with a balance.  Optionally enter a number of days to pull up a report only including customers with balances over the specified number of days old.&lt;br /&gt;
&lt;br /&gt;
=== Prepaid income ===&lt;br /&gt;
&lt;br /&gt;
=== Sales tax liability ===&lt;br /&gt;
&lt;br /&gt;
* See [[Freeside:3:Documentation:User:Tax_liability_report]].&lt;br /&gt;
&lt;br /&gt;
= Tools =&lt;br /&gt;
&lt;br /&gt;
== Quick Payment Entry ==&lt;br /&gt;
&lt;br /&gt;
== Tower coverage estimation ==&lt;br /&gt;
&lt;br /&gt;
[[Freeside:3:Documentation:User:Tower_coverage_estimation|Enter your tower locations and display their expected coverage area.]]&lt;br /&gt;
&lt;br /&gt;
= Auditing Legacy Data =&lt;br /&gt;
&lt;br /&gt;
http://www.freeside.biz/~ivan/freeside-slides/html/slide_15.html&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:User:Tower_coverage_estimation&amp;diff=9560</id>
		<title>Freeside:3:Documentation:User:Tower coverage estimation</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:User:Tower_coverage_estimation&amp;diff=9560"/>
				<updated>2016-05-24T20:22:07Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Created page with &amp;quot;== Tower coverage estimation ==  Freeside now has simple capabilities to estimate the coverage areas of RF towers. To use this, you will need: * A tower and sector entry for t...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Tower coverage estimation ==&lt;br /&gt;
&lt;br /&gt;
Freeside now has simple capabilities to estimate the coverage areas of RF towers. To use this, you will need:&lt;br /&gt;
* A tower and sector entry for the radio site, with coordinates and mast height.&lt;br /&gt;
* The antenna direction (0 - 360 degrees) and downtilt.&lt;br /&gt;
* The horizontal and vertical beamwidth of the antenna.&lt;br /&gt;
* A minimum signal margin for a site to be considered &amp;quot;covered&amp;quot;. This is generally (transmit power) + (antenna gains) - (minimum RSSI). Most WISP operators should be able to determine this.&lt;br /&gt;
&lt;br /&gt;
=== Generating a map ===&lt;br /&gt;
* Open the tower list (Configuration / Services / Wireless broadband / Towers).&lt;br /&gt;
* Click &amp;quot;Sector coverage maps&amp;quot;.&lt;br /&gt;
* Find the entry for your tower.&lt;br /&gt;
** If any of the required fields are missing, the column to the right will list them. If so, click the tower name to edit the tower and sector, then come back.&lt;br /&gt;
* Click &amp;quot;Create map&amp;quot;. This may take some time as the terrain maps are downloaded.&lt;br /&gt;
* There will now be a link for &amp;quot;View map--X margin&amp;quot;. Click that to open the map.&lt;br /&gt;
&lt;br /&gt;
=== Limitations ===&lt;br /&gt;
* This only works in North America. SRTM data is available for anywhere on Earth, but right now the software doesn't know the other download paths.&lt;br /&gt;
* The map won't display unless there's at least one broadband service attached to the sector (because it's actually the svc_broadband map for the sector).&lt;br /&gt;
* We can only show one signal margin on the map at a time.&lt;br /&gt;
* Similarly, we only show the map for one sector at a time. It would be nice to at least show the entire tower.&lt;br /&gt;
* All of these can be fixed.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Administration:RT_Workflow&amp;diff=9520</id>
		<title>Freeside:3:Documentation:Administration:RT Workflow</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Administration:RT_Workflow&amp;diff=9520"/>
				<updated>2015-12-15T20:59:22Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Subtasks==&lt;br /&gt;
&lt;br /&gt;
[[File:setup_subtasks.png|500px|right]]&lt;br /&gt;
Freeside 3.8 and above have a streamlined interface for setting up &amp;quot;Create Tickets&amp;quot; scrips in the typical case where some type of ticket will have a standard set of dependent tasks. To define subtasks for a ticket queue:&lt;br /&gt;
&lt;br /&gt;
# Create a queue for this type of ticket, if you don't have one yet. Each queue can have only one set of subtasks.&lt;br /&gt;
# In the queue configuration, open the &amp;quot;Subtasks&amp;quot; tab on the top bar.&lt;br /&gt;
# For the first subtask, enter the subject and message for the dependent ticket.&lt;br /&gt;
#* Optionally, you can set the tickets to be created in a different queue.&lt;br /&gt;
#* You can check the &amp;quot;Prefix with main subject&amp;quot; box to have the subject line prefixed with the subject from the parent ticket.&lt;br /&gt;
# Click &amp;quot;Save changes&amp;quot;.&lt;br /&gt;
# The page will refresh with space for a second subtask. Repeat as necessary for each subtask you want to create.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When adding a ticket to the queue, there will be a dropdown box labeled &amp;quot;Create subtasks&amp;quot;. Set this to &amp;quot;Yes&amp;quot; and subtask tickets will be created. You can also create the ticket first, then set this field to &amp;quot;Yes&amp;quot; later to start the subtasks.&lt;br /&gt;
[[File:create_ticket.png|500px|right]]&lt;br /&gt;
&lt;br /&gt;
=== Implementation ===&lt;br /&gt;
&lt;br /&gt;
Internally, setting up subtasks creates the following objects:&lt;br /&gt;
* A ticket custom field named &amp;quot;Create subtasks&amp;quot;, applied to the queue. This controls activation of subtasks on a per-ticket basis.&lt;br /&gt;
* A scrip condition that tests that custom field and activates when its value is set to &amp;quot;Yes&amp;quot;.&lt;br /&gt;
* A template for the &amp;quot;CreateTickets&amp;quot; scrip action, containing the subtask subject and content and some standard ticket parameters.&lt;br /&gt;
** In particular, the subtask always inherits the parent ticket's requestor and owner.&lt;br /&gt;
** The parent ticket always has the subtask ticket added as a dependency, so it can't be resolved until all subtasks are complete.&lt;br /&gt;
* A scrip using that scrip condition and template. The scrip will trigger when the ticket is created with &amp;quot;Create subtasks = Yes&amp;quot;, or when &amp;quot;Create subtasks&amp;quot; is set to &amp;quot;Yes&amp;quot; by a later transaction.&lt;br /&gt;
&lt;br /&gt;
The subject and content can include Perl code in { braces }. The main ticket is available as the variable &amp;quot;$TOP&amp;quot; (an RT::Ticket object).&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:Create_ticket.png&amp;diff=9519</id>
		<title>File:Create ticket.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:Create_ticket.png&amp;diff=9519"/>
				<updated>2015-12-15T20:54:09Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=File:Setup_subtasks.png&amp;diff=9518</id>
		<title>File:Setup subtasks.png</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=File:Setup_subtasks.png&amp;diff=9518"/>
				<updated>2015-12-15T20:53:31Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Administration:RT_Workflow&amp;diff=9517</id>
		<title>Freeside:3:Documentation:Administration:RT Workflow</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:3:Documentation:Administration:RT_Workflow&amp;diff=9517"/>
				<updated>2015-12-15T20:53:04Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Documentation for RT subtasks&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Subtasks==&lt;br /&gt;
&lt;br /&gt;
Freeside 3.8 and above have a streamlined interface for setting up &amp;quot;Create Tickets&amp;quot; scrips in the typical case where some type of ticket will have a standard set of dependent tasks. To define subtasks for a ticket queue:&lt;br /&gt;
&lt;br /&gt;
# Create a queue for this type of ticket, if you don't have one yet. Each queue can have only one set of subtasks.&lt;br /&gt;
# In the queue configuration, open the &amp;quot;Subtasks&amp;quot; tab on the top bar.&lt;br /&gt;
# For the first subtask, enter the subject and message for the dependent ticket.&lt;br /&gt;
#* Optionally, you can set the tickets to be created in a different queue.&lt;br /&gt;
#* You can check the &amp;quot;Prefix with main subject&amp;quot; box to have the subject line prefixed with the subject from the parent ticket.&lt;br /&gt;
# Click &amp;quot;Save changes&amp;quot;.&lt;br /&gt;
# The page will refresh with space for a second subtask. Repeat as necessary for each subtask you want to create.&lt;br /&gt;
&lt;br /&gt;
[[File:setup_subtasks.png]]&lt;br /&gt;
&lt;br /&gt;
When adding a ticket to the queue, there will be a dropdown box labeled &amp;quot;Create subtasks&amp;quot;. Set this to &amp;quot;Yes&amp;quot; and subtask tickets will be created. You can also create the ticket first, then set this field to &amp;quot;Yes&amp;quot; later to start the subtasks.&lt;br /&gt;
[[File:create_ticket.png]]&lt;br /&gt;
&lt;br /&gt;
=== Implementation ===&lt;br /&gt;
&lt;br /&gt;
Internally, setting up subtasks creates the following objects:&lt;br /&gt;
* A ticket custom field named &amp;quot;Create subtasks&amp;quot;, applied to the queue. This controls activation of subtasks on a per-ticket basis.&lt;br /&gt;
* A scrip condition that tests that custom field and activates when its value is set to &amp;quot;Yes&amp;quot;.&lt;br /&gt;
* A template for the &amp;quot;CreateTickets&amp;quot; scrip action, containing the subtask subject and content and some standard ticket parameters.&lt;br /&gt;
** In particular, the subtask always inherits the parent ticket's requestor and owner.&lt;br /&gt;
** The parent ticket always has the subtask ticket added as a dependency, so it can't be resolved until all subtasks are complete.&lt;br /&gt;
* A scrip using that scrip condition and template. The scrip will trigger when the ticket is created with &amp;quot;Create subtasks = Yes&amp;quot;, or when &amp;quot;Create subtasks&amp;quot; is set to &amp;quot;Yes&amp;quot; by a later transaction.&lt;br /&gt;
&lt;br /&gt;
The subject and content can include Perl code in { braces }. The main ticket is available as the variable &amp;quot;$TOP&amp;quot; (an RT::Ticket object).&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Test_Suite&amp;diff=8973</id>
		<title>Test Suite</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Test_Suite&amp;diff=8973"/>
				<updated>2015-08-31T21:55:20Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Freeside Test Suite =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Freeside now has a small automated test suite, consisting of:&lt;br /&gt;
&lt;br /&gt;
* a database image in a known state, containing randomly generated customers and services&lt;br /&gt;
* a script of URLs to load from the web server&lt;br /&gt;
* reference copies of those pages&lt;br /&gt;
* some simple tools to load the database image, fetch the URLs, and compare them to the reference copies&lt;br /&gt;
&lt;br /&gt;
Currently the tests cover only a small, commonly used subset of the UI, and a relatively simple billing setup.&lt;br /&gt;
&lt;br /&gt;
== Running the tests ==&lt;br /&gt;
&lt;br /&gt;
&amp;quot;freeside-test-run&amp;quot; is the main test script. Currently there's only one test&lt;br /&gt;
plan, &amp;quot;ui_tests&amp;quot;. freeside-test-run will:&lt;br /&gt;
&lt;br /&gt;
* download all the URLs listed in the test plan into a directory in /tmp&lt;br /&gt;
* compare them to the reference versions with &amp;quot;diff -ur&amp;quot;&lt;br /&gt;
* write the output to &amp;quot;freeside_test.YYYYMMDD.diff&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The raw output directory will not be deleted, so you can examine the results&lt;br /&gt;
yourself.&lt;br /&gt;
&lt;br /&gt;
If you want to do anything with the database besides compare the test results&lt;br /&gt;
to reference, run &amp;quot;freeside-test-start&amp;quot; by hand first. This will create a&lt;br /&gt;
database with the test image and start Apache with a fake time of one day after&lt;br /&gt;
the last bill. If there's an existing Freeside database, it will be renamed to&lt;br /&gt;
&amp;quot;freeside_YYYYMMDD&amp;quot; (the current date).&lt;br /&gt;
&lt;br /&gt;
To put the existing database back in place, run &amp;quot;freeside-test-stop&amp;quot;, then&lt;br /&gt;
restart Apache and any Freeside services.&lt;br /&gt;
&lt;br /&gt;
== Updating the reference pages ==&lt;br /&gt;
&lt;br /&gt;
The simplest way to update the reference copies of the test pages is&lt;br /&gt;
&lt;br /&gt;
 bin/freeside-test-start&lt;br /&gt;
 bin/freeside-test-fetch -d ./share/output&lt;br /&gt;
&lt;br /&gt;
(from the FS-Test source directory). If you're installing from a git repo,&lt;br /&gt;
this will overwrite the working tree with the newly downloaded test pages.&lt;br /&gt;
You can then use &amp;quot;git diff&amp;quot; to examine your changes and &amp;quot;git commit&amp;quot; to&lt;br /&gt;
commit them when you're done.&lt;br /&gt;
&lt;br /&gt;
== Adding new tests ==&lt;br /&gt;
&lt;br /&gt;
For UI tests, add the URL, minus the &amp;quot;http://hostname/freeside&amp;quot; prefix,&lt;br /&gt;
to share/ui_tests. Then run freeside-test-fetch to get the reference output&lt;br /&gt;
of the test and copy it to share/output (and add that file to git).&lt;br /&gt;
&lt;br /&gt;
When adding any new test, make sure to run it twice on the same database&lt;br /&gt;
and code version to detect random or time-sensitive variations. These will&lt;br /&gt;
throw false alerts when anyone else uses your test, so you'll need to suppress&lt;br /&gt;
them somehow.&lt;br /&gt;
&lt;br /&gt;
== What's in the test database ==&lt;br /&gt;
&lt;br /&gt;
Two hundred customers with random nonsensical U.S. addresses. One third have&lt;br /&gt;
automatic checking, the rest have credit cards. 10% of the customers have&lt;br /&gt;
separate billing and service addresses.&lt;br /&gt;
&lt;br /&gt;
Four package definitions (plus the unique &amp;quot;system domain&amp;quot;): annual domain,&lt;br /&gt;
monthly prorated account, monthly prorated broadband service, monthly&lt;br /&gt;
phone. Each package has one service except the phone package, which has a&lt;br /&gt;
quantity limit of 4. The two monthly prorated packages have setup fees.&lt;br /&gt;
&lt;br /&gt;
Each customer has three of the four packages, started over the period from&lt;br /&gt;
2015-08-01 to 2016-03-01. Each package has a service configured (with a random&lt;br /&gt;
username, domain name, phone number, or IP address).  Customers were invoiced&lt;br /&gt;
on the first day of each month during that period.&lt;br /&gt;
&lt;br /&gt;
There's a billing event to run automatic credit card billing on eligible&lt;br /&gt;
invoices. The configured gateway is Business::OnlinePayment::Dummy, which&lt;br /&gt;
returns success for everything, so customers with credit cards will be fully&lt;br /&gt;
paid.&lt;br /&gt;
&lt;br /&gt;
One user with unlimited access. Username &amp;quot;test&amp;quot;, password &amp;quot;test&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
There are no cancellations or suspensions, no credits, no taxes, no refunds,&lt;br /&gt;
no discounts, no exports, and no RT tickets.  All of this stuff lies in the&lt;br /&gt;
bright shining future.&lt;br /&gt;
&lt;br /&gt;
If you modify the test database, be sure to update this section also. Also&lt;br /&gt;
update the company name to &amp;quot;Freeside Test X.Y.Z&amp;quot; where X.Y is the Freeside&lt;br /&gt;
version number and Z is the test database version number, and set the same&lt;br /&gt;
value in freeside-test-start so that old test databases aren't used by&lt;br /&gt;
mistake.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Test_Suite&amp;diff=8972</id>
		<title>Test Suite</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Test_Suite&amp;diff=8972"/>
				<updated>2015-08-31T21:54:27Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Freeside Test Suite =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Freeside now has a small automated test suite, consisting of:&lt;br /&gt;
&lt;br /&gt;
 - a database image in a known state, containing randomly generated customers and services&lt;br /&gt;
 - a script of URLs to load from the web server&lt;br /&gt;
 - reference copies of those pages&lt;br /&gt;
 - some simple tools to load the database image, fetch the URLs, and compare them to the reference copies&lt;br /&gt;
&lt;br /&gt;
Currently the tests cover only a small, commonly used subset of the UI, and a relatively simple billing setup.&lt;br /&gt;
&lt;br /&gt;
== Running the tests ==&lt;br /&gt;
&lt;br /&gt;
&amp;quot;freeside-test-run&amp;quot; is the main test script. Currently there's only one test&lt;br /&gt;
plan, &amp;quot;ui_tests&amp;quot;. freeside-test-run will:&lt;br /&gt;
&lt;br /&gt;
- download all the URLs listed in the test plan into a directory in /tmp&lt;br /&gt;
- compare them to the reference versions with &amp;quot;diff -ur&amp;quot;&lt;br /&gt;
- write the output to &amp;quot;freeside_test.YYYYMMDD.diff&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The raw output directory will not be deleted, so you can examine the results&lt;br /&gt;
yourself.&lt;br /&gt;
&lt;br /&gt;
If you want to do anything with the database besides compare the test results&lt;br /&gt;
to reference, run &amp;quot;freeside-test-start&amp;quot; by hand first. This will create a&lt;br /&gt;
database with the test image and start Apache with a fake time of one day after&lt;br /&gt;
the last bill. If there's an existing Freeside database, it will be renamed to&lt;br /&gt;
&amp;quot;freeside_YYYYMMDD&amp;quot; (the current date).&lt;br /&gt;
&lt;br /&gt;
To put the existing database back in place, run &amp;quot;freeside-test-stop&amp;quot;, then&lt;br /&gt;
restart Apache and any Freeside services.&lt;br /&gt;
&lt;br /&gt;
== Updating the reference pages ==&lt;br /&gt;
&lt;br /&gt;
The simplest way to update the reference copies of the test pages is&lt;br /&gt;
&lt;br /&gt;
bin/freeside-test-start&lt;br /&gt;
bin/freeside-test-fetch -d ./share/output&lt;br /&gt;
&lt;br /&gt;
(from the FS-Test source directory). If you're installing from a git repo,&lt;br /&gt;
this will overwrite the working tree with the newly downloaded test pages.&lt;br /&gt;
You can then use &amp;quot;git diff&amp;quot; to examine your changes and &amp;quot;git commit&amp;quot; to&lt;br /&gt;
commit them when you're done.&lt;br /&gt;
&lt;br /&gt;
== Adding new tests ==&lt;br /&gt;
&lt;br /&gt;
For UI tests, add the URL, minus the &amp;quot;http://hostname/freeside&amp;quot; prefix,&lt;br /&gt;
to share/ui_tests. Then run freeside-test-fetch to get the reference output&lt;br /&gt;
of the test and copy it to share/output (and add that file to git).&lt;br /&gt;
&lt;br /&gt;
When adding any new test, make sure to run it twice on the same database&lt;br /&gt;
and code version to detect random or time-sensitive variations. These will&lt;br /&gt;
throw false alerts when anyone else uses your test, so you'll need to suppress&lt;br /&gt;
them somehow.&lt;br /&gt;
&lt;br /&gt;
== What's in the test database ==&lt;br /&gt;
&lt;br /&gt;
Two hundred customers with random nonsensical U.S. addresses. One third have&lt;br /&gt;
automatic checking, the rest have credit cards. 10% of the customers have&lt;br /&gt;
separate billing and service addresses.&lt;br /&gt;
&lt;br /&gt;
Four package definitions (plus the unique &amp;quot;system domain&amp;quot;): annual domain,&lt;br /&gt;
monthly prorated account, monthly prorated broadband service, monthly&lt;br /&gt;
phone. Each package has one service except the phone package, which has a&lt;br /&gt;
quantity limit of 4. The two monthly prorated packages have setup fees.&lt;br /&gt;
&lt;br /&gt;
Each customer has three of the four packages, started over the period from&lt;br /&gt;
2015-08-01 to 2016-03-01. Each package has a service configured (with a random&lt;br /&gt;
username, domain name, phone number, or IP address).  Customers were invoiced&lt;br /&gt;
on the first day of each month during that period.&lt;br /&gt;
&lt;br /&gt;
There's a billing event to run automatic credit card billing on eligible&lt;br /&gt;
invoices. The configured gateway is Business::OnlinePayment::Dummy, which&lt;br /&gt;
returns success for everything, so customers with credit cards will be fully&lt;br /&gt;
paid.&lt;br /&gt;
&lt;br /&gt;
One user with unlimited access. Username &amp;quot;test&amp;quot;, password &amp;quot;test&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
There are no cancellations or suspensions, no credits, no taxes, no refunds,&lt;br /&gt;
no discounts, no exports, and no RT tickets.  All of this stuff lies in the&lt;br /&gt;
bright shining future.&lt;br /&gt;
&lt;br /&gt;
If you modify the test database, be sure to update this section also. Also&lt;br /&gt;
update the company name to &amp;quot;Freeside Test X.Y.Z&amp;quot; where X.Y is the Freeside&lt;br /&gt;
version number and Z is the test database version number, and set the same&lt;br /&gt;
value in freeside-test-start so that old test databases aren't used by&lt;br /&gt;
mistake.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Test_Suite&amp;diff=8971</id>
		<title>Test Suite</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Test_Suite&amp;diff=8971"/>
				<updated>2015-08-31T21:54:07Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Copy FS-Test/README, clean up a little&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Freeside Test Suite =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Freeside now has a small automated test suite, consisting of:&lt;br /&gt;
&lt;br /&gt;
- a database image in a known state, containing randomly generated customers and services&lt;br /&gt;
- a script of URLs to load from the web server&lt;br /&gt;
- reference copies of those pages&lt;br /&gt;
- some simple tools to load the database image, fetch the URLs, and compare them to the reference copies&lt;br /&gt;
&lt;br /&gt;
Currently the tests cover only a small, commonly used subset of the UI, and a relatively simple billing setup.&lt;br /&gt;
&lt;br /&gt;
== Running the tests ==&lt;br /&gt;
&lt;br /&gt;
&amp;quot;freeside-test-run&amp;quot; is the main test script. Currently there's only one test&lt;br /&gt;
plan, &amp;quot;ui_tests&amp;quot;. freeside-test-run will:&lt;br /&gt;
&lt;br /&gt;
- download all the URLs listed in the test plan into a directory in /tmp&lt;br /&gt;
- compare them to the reference versions with &amp;quot;diff -ur&amp;quot;&lt;br /&gt;
- write the output to &amp;quot;freeside_test.YYYYMMDD.diff&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The raw output directory will not be deleted, so you can examine the results&lt;br /&gt;
yourself.&lt;br /&gt;
&lt;br /&gt;
If you want to do anything with the database besides compare the test results&lt;br /&gt;
to reference, run &amp;quot;freeside-test-start&amp;quot; by hand first. This will create a&lt;br /&gt;
database with the test image and start Apache with a fake time of one day after&lt;br /&gt;
the last bill. If there's an existing Freeside database, it will be renamed to&lt;br /&gt;
&amp;quot;freeside_YYYYMMDD&amp;quot; (the current date).&lt;br /&gt;
&lt;br /&gt;
To put the existing database back in place, run &amp;quot;freeside-test-stop&amp;quot;, then&lt;br /&gt;
restart Apache and any Freeside services.&lt;br /&gt;
&lt;br /&gt;
== Updating the reference pages ==&lt;br /&gt;
&lt;br /&gt;
The simplest way to update the reference copies of the test pages is&lt;br /&gt;
&lt;br /&gt;
bin/freeside-test-start&lt;br /&gt;
bin/freeside-test-fetch -d ./share/output&lt;br /&gt;
&lt;br /&gt;
(from the FS-Test source directory). If you're installing from a git repo,&lt;br /&gt;
this will overwrite the working tree with the newly downloaded test pages.&lt;br /&gt;
You can then use &amp;quot;git diff&amp;quot; to examine your changes and &amp;quot;git commit&amp;quot; to&lt;br /&gt;
commit them when you're done.&lt;br /&gt;
&lt;br /&gt;
== Adding new tests ==&lt;br /&gt;
&lt;br /&gt;
For UI tests, add the URL, minus the &amp;quot;http://hostname/freeside&amp;quot; prefix,&lt;br /&gt;
to share/ui_tests. Then run freeside-test-fetch to get the reference output&lt;br /&gt;
of the test and copy it to share/output (and add that file to git).&lt;br /&gt;
&lt;br /&gt;
When adding any new test, make sure to run it twice on the same database&lt;br /&gt;
and code version to detect random or time-sensitive variations. These will&lt;br /&gt;
throw false alerts when anyone else uses your test, so you'll need to suppress&lt;br /&gt;
them somehow.&lt;br /&gt;
&lt;br /&gt;
== What's in the test database ==&lt;br /&gt;
&lt;br /&gt;
Two hundred customers with random nonsensical U.S. addresses. One third have&lt;br /&gt;
automatic checking, the rest have credit cards. 10% of the customers have&lt;br /&gt;
separate billing and service addresses.&lt;br /&gt;
&lt;br /&gt;
Four package definitions (plus the unique &amp;quot;system domain&amp;quot;): annual domain,&lt;br /&gt;
monthly prorated account, monthly prorated broadband service, monthly&lt;br /&gt;
phone. Each package has one service except the phone package, which has a&lt;br /&gt;
quantity limit of 4. The two monthly prorated packages have setup fees.&lt;br /&gt;
&lt;br /&gt;
Each customer has three of the four packages, started over the period from&lt;br /&gt;
2015-08-01 to 2016-03-01. Each package has a service configured (with a random&lt;br /&gt;
username, domain name, phone number, or IP address).  Customers were invoiced&lt;br /&gt;
on the first day of each month during that period.&lt;br /&gt;
&lt;br /&gt;
There's a billing event to run automatic credit card billing on eligible&lt;br /&gt;
invoices. The configured gateway is Business::OnlinePayment::Dummy, which&lt;br /&gt;
returns success for everything, so customers with credit cards will be fully&lt;br /&gt;
paid.&lt;br /&gt;
&lt;br /&gt;
One user with unlimited access. Username &amp;quot;test&amp;quot;, password &amp;quot;test&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
There are no cancellations or suspensions, no credits, no taxes, no refunds,&lt;br /&gt;
no discounts, no exports, and no RT tickets.  All of this stuff lies in the&lt;br /&gt;
bright shining future.&lt;br /&gt;
&lt;br /&gt;
If you modify the test database, be sure to update this section also. Also&lt;br /&gt;
update the company name to &amp;quot;Freeside Test X.Y.Z&amp;quot; where X.Y is the Freeside&lt;br /&gt;
version number and Z is the test database version number, and set the same&lt;br /&gt;
value in freeside-test-start so that old test databases aren't used by&lt;br /&gt;
mistake.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=8970</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=8970"/>
				<updated>2015-08-31T21:44:53Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Freeside ==&lt;br /&gt;
=== Versions ===&lt;br /&gt;
&lt;br /&gt;
:'''Stable Version:''' 3.7&lt;br /&gt;
::released Jun 26, 2015&lt;br /&gt;
&lt;br /&gt;
:'''Development Version:''': 4.x&lt;br /&gt;
::2015&lt;br /&gt;
&lt;br /&gt;
:'''Future''': 5.x / git master&lt;br /&gt;
::2016?&lt;br /&gt;
&lt;br /&gt;
=== Turn Key Solutions ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#install Installation]&lt;br /&gt;
*[http://www.freeside.biz/freeside/products.html Freeside Appliance]&lt;br /&gt;
&lt;br /&gt;
=== Documentation ===&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:2.3:Documentation|2.3 Documentation]]&lt;br /&gt;
* [[Freeside:3:Documentation|3.x Documentation]]&lt;br /&gt;
* [[Freeside:4:Documentation|4.x Documentation]]&lt;br /&gt;
&lt;br /&gt;
=== Support ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#support Freeside Internet Services Inc.]&lt;br /&gt;
*[[Freeside:Support:Consultants | Freeside Consultants]]&lt;br /&gt;
*[[Freeside:Support:HelpWanted | Help Wanted]]&lt;br /&gt;
&lt;br /&gt;
===Training===&lt;br /&gt;
*[[Training syllabus]]&lt;br /&gt;
&lt;br /&gt;
=== Specs in progress ===&lt;br /&gt;
* [[Use Cases]]&lt;br /&gt;
* [[Business::FraudDetect]]&lt;br /&gt;
* [[Event_Refactor]]&lt;br /&gt;
* [[UI_Refactor]]&lt;br /&gt;
* [[Website_Refactor]]&lt;br /&gt;
* [[Browser_support]]&lt;br /&gt;
* [[Test Suite]]&lt;br /&gt;
&lt;br /&gt;
=== Historical ===&lt;br /&gt;
* [[Batch_Refactor]]&lt;br /&gt;
* [[Broadband_Services_Spec]]&lt;br /&gt;
* [[Virtual_to_Real_Fields]]&lt;br /&gt;
* [[part_pkg Mixin Refactor]]&lt;br /&gt;
* A new [[Version_Control_System]]&lt;br /&gt;
* [[RPM_Build_system]]&lt;br /&gt;
&lt;br /&gt;
= Third party software =&lt;br /&gt;
&lt;br /&gt;
[[3rd_party_software|Third party software]] related to Freeside.&lt;br /&gt;
&lt;br /&gt;
= WIKI Reference =&lt;br /&gt;
&lt;br /&gt;
[http://meta.wikimedia.org/wiki/Help:Editing How to edit pages (wiki markup, etc)]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://www.mediawiki.org/wiki/Help:Configuration_settings Configuration settings list]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://meta.wikipedia.org/wiki/MediaWiki_User%27s_Guide User's Guide]&lt;br /&gt;
&lt;br /&gt;
[[Sandbox]] &amp;amp;larr; Use this page to test out editing, [http://www.phrases.org.uk/meanings/225200.html learn the ropes], etc.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=8969</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Main_Page&amp;diff=8969"/>
				<updated>2015-08-31T21:38:01Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: /* Specs in progress */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Freeside ==&lt;br /&gt;
=== Versions ===&lt;br /&gt;
&lt;br /&gt;
:'''Stable Version:''' 3.7&lt;br /&gt;
::released Jun 26, 2015&lt;br /&gt;
&lt;br /&gt;
:'''Development Version:''': 4.x&lt;br /&gt;
::2015&lt;br /&gt;
&lt;br /&gt;
:'''Future''': 5.x / git master&lt;br /&gt;
::2016?&lt;br /&gt;
&lt;br /&gt;
=== Turn Key Solutions ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#install Installation]&lt;br /&gt;
*[http://www.freeside.biz/freeside/products.html Freeside Appliance]&lt;br /&gt;
&lt;br /&gt;
=== Documentation ===&lt;br /&gt;
&lt;br /&gt;
* [[Freeside:2.3:Documentation|2.3 Documentation]]&lt;br /&gt;
* [[Freeside:3:Documentation|3.x Documentation]]&lt;br /&gt;
* [[Freeside:4:Documentation|4.x Documentation]]&lt;br /&gt;
&lt;br /&gt;
=== Support ===&lt;br /&gt;
*[http://www.freeside.biz/freeside/services.html#support Freeside Internet Services Inc.]&lt;br /&gt;
*[[FreeSide:Support:Consultants | Freeside Consultants]]&lt;br /&gt;
*[[FreeSide:Support:HelpWanted | Help Wanted]]&lt;br /&gt;
&lt;br /&gt;
===Training===&lt;br /&gt;
*[[Training syllabus]]&lt;br /&gt;
&lt;br /&gt;
=== Specs in progress ===&lt;br /&gt;
* [[Use Cases]]&lt;br /&gt;
* [[Business::FraudDetect]]&lt;br /&gt;
* [[Event_Refactor]]&lt;br /&gt;
* [[UI_Refactor]]&lt;br /&gt;
* [[Website_Refactor]]&lt;br /&gt;
* [[Browser_support]]&lt;br /&gt;
* [[Testing Tool]]&lt;br /&gt;
&lt;br /&gt;
=== Historical ===&lt;br /&gt;
* [[Batch_Refactor]]&lt;br /&gt;
* [[Broadband_Services_Spec]]&lt;br /&gt;
* [[Virtual_to_Real_Fields]]&lt;br /&gt;
* [[part_pkg Mixin Refactor]]&lt;br /&gt;
* A new [[Version_Control_System]]&lt;br /&gt;
* [[RPM_Build_system]]&lt;br /&gt;
&lt;br /&gt;
= Third party software =&lt;br /&gt;
&lt;br /&gt;
[[3rd_party_software|Third party software]] related to Freeside.&lt;br /&gt;
&lt;br /&gt;
= WIKI Reference =&lt;br /&gt;
&lt;br /&gt;
[http://meta.wikimedia.org/wiki/Help:Editing How to edit pages (wiki markup, etc)]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://www.mediawiki.org/wiki/Help:Configuration_settings Configuration settings list]&amp;lt;br&amp;gt;&lt;br /&gt;
[http://meta.wikipedia.org/wiki/MediaWiki_User%27s_Guide User's Guide]&lt;br /&gt;
&lt;br /&gt;
[[Sandbox]] &amp;amp;larr; Use this page to test out editing, [http://www.phrases.org.uk/meanings/225200.html learn the ropes], etc.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	<entry>
		<id>https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:ExternalMessaging&amp;diff=8968</id>
		<title>Freeside:4:Documentation:ExternalMessaging</title>
		<link rel="alternate" type="text/html" href="https://secure.freeside.biz/mediawiki/index.php?title=Freeside:4:Documentation:ExternalMessaging&amp;diff=8968"/>
				<updated>2015-08-31T01:07:05Z</updated>
		
		<summary type="html">&lt;p&gt;Mark: Some basic docs for this feature&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating the messaging service ==&lt;br /&gt;
&lt;br /&gt;
The Freeside external messaging interface is a REST client. It communicates with the service by POSTing to specified URLs (which can be different for each kind of message, but need not be). The body of the POST is always a JSON object.&lt;br /&gt;
&lt;br /&gt;
Messages are processed in two stages. At either stage the server can report failure by returning an HTTP failure status (non-200). The body of the HTTP response will be saved as the error message.&lt;br /&gt;
&lt;br /&gt;
=== Prepare ===&lt;br /&gt;
Freeside POSTs a JSON object containing all of the same substitution variables available to internal message templates. See http://localhost/freeside/edit/msg_template/email.html for a list of these.&lt;br /&gt;
The element names in the object are the same as the variable names, minus the '$' sigil. The messaging service must return a document containing the &amp;quot;prepared message&amp;quot;: everything it needs to successfully send the message later. This should be a JSON document, though that's not yet enforced (as of September 2015).&lt;br /&gt;
&lt;br /&gt;
At this stage the user might still decide not to send the message, so the messaging service shouldn't do anything irreversible yet.&lt;br /&gt;
&lt;br /&gt;
=== Send ===&lt;br /&gt;
Freeside POSTs the prepared message verbatim. At this point the messaging service should deliver it to the customer.&lt;br /&gt;
&lt;br /&gt;
On success, the body of the response message will be ignored.&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
&lt;br /&gt;
There's an example REST server in bin/msg_template_http-demo.pl, using Mojolicious. It shows how to take substitution variables, insert them into a body of text, and return a JSON object representing the message to be sent, and then (at the Send stage) how to take that object and turn it into an email message.&lt;br /&gt;
&lt;br /&gt;
== Configuring the message interface in Freeside ==&lt;br /&gt;
&lt;br /&gt;
From the menu bar, open '''Configuration -&amp;gt; Miscellaneous -&amp;gt; Message templates'''. Click the link for '''External message interfaces''', then '''Add a new interface'''.&lt;br /&gt;
&lt;br /&gt;
'''Agent''' is which agent will have access to this interface (or &amp;quot;all&amp;quot; for it to be globally available).&lt;br /&gt;
'''Interface name''' is a descriptive name for your use.&lt;br /&gt;
'''Prepare URL''' and '''Send URL''' are the URLs to be POSTed to in the Prepare and Send stages, respectively. No variable substitution is performed on these.&lt;br /&gt;
'''HTTP username''' and '''password''' are a login for Basic authentication. This is optional.&lt;br /&gt;
'''Additional POST content''' is an optional JSON object (a complete object, including surrounding { } delimiters) to be included in the Prepare document. All the standard substitution variables will be added to it. If you don't specify one, you'll send an object containing only the standard variables.&lt;/div&gt;</summary>
		<author><name>Mark</name></author>	</entry>

	</feed>