This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
khosb/modules/invoice/invoice.inc.php
2011-05-03 09:49:04 +10:00

3500 lines
113 KiB
PHP

<?php
/**
* AgileBill - Open Billing Software
*
* This body of work is free software; you can redistribute it and/or
* modify it under the terms of the Open AgileBill License
* License as published at http://www.agileco.com/agilebill/license1-4.txt
*
* Originally authored by Tony Landis, AgileBill LLC
*
* Recent modifications by Deon George
*
* @author Deon George <deonATleenooksDOTnet>
* @copyright 2009 Deon George
* @link http://osb.leenooks.net
*
* @link http://www.agileco.com/
* @copyright 2004-2008 Agileco, LLC.
* @license http://www.agileco.com/agilebill/license1-4.txt
* @author Tony Landis <tony@agileco.com>
* @package AgileBill
* @subpackage Modules:Invoice
*/
/**
* The main AgileBill Invoice Class
*
* @package AgileBill
* @subpackage Modules:Invoice
*/
class invoice extends OSB_module {
# Hold the invoice items associated with this invoice
private $items = array();
# Array holding all our print information
public $print = array();
# Enable summary invoice view that rolls multiple instances of the same sku w/identical base&setup price & attributes into one line item
private $summarizeInvoice = true;
/**
* Delete an invoice
*
* @uses service
*/
public function delete($VAR) {
$db = &DB();
# Get the array
if (isset($VAR['delete_id']))
$ids = explode(',',preg_replace('/,$/','',$VAR['delete_id']));
elseif (isset($VAR['id']))
$ids = explode(',',preg_replace('/,$/','',$VAR['id']));
# Load the service module
include_once(PATH_MODULES.'service/service.inc.php');
$so = new service;
foreach ($ids as $id) {
# Loop through all services for this invoice and delete:
$rs = $db->Execute(sqlSelect('service',array('where'=>array('invoice_id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
if ($rs->RecordCount()) {
while (! $rs->EOF) {
$so->delete($rs->fields['id']);
$rs->MoveNext();
}
}
# Delete the service record
$this->associated_DELETE = array();
array_push($this->associated_DELETE,array('table'=>'invoice_commission','field'=>'invoice_id'));
array_push($this->associated_DELETE,array('table'=>'invoice_item','field'=>'invoice_id'));
array_push($this->associated_DELETE,array('table'=>'invoice_memo','field'=>'invoice_id'));
// array_push($this->associated_DELETE,array('table'=>'service','field'=>'invoice_id'));
array_push($this->associated_DELETE,array('table'=>'invoice_item_tax','field'=>'invoice_id'));
array_push($this->associated_DELETE,array('table'=>'invoice_item_discount','field'=>'invoice_id'));
$result = parent::delete($VAR);
}
}
/**
* View an Invoice
* Shown both in the admin pages and after checkout
*
* @uses net_term
*/
public function view($VAR) {
global $C_translate,$C_list;
$db = &DB();
if ($smart = parent::view($VAR)) {
# Get the product checkout plugin name
if (! empty($smart['checkout_plugin_id'])) {
$rs = $db->Execute(sqlSelect('checkout','name',array('where'=>array('id'=>$smart['checkout_plugin_id']))));
if ($rs && $rs->RecordCount())
$smart['checkout_plugin'] = $rs->fields['name'];
}
$smart['balance'] = ($smart['total_amt'] == 0) ? 0 : $smart['total_amt']-$smart['billed_amt']-$smart['credit_amt'];
# Get the tax details
if (! empty($smart['tax_amt'])) {
$rs = $db->Execute(sqlSelect($db,array('invoice_item_tax','tax'),'A.amount,B.description',sprintf('A.tax_id=B.id AND A.invoice_id=%s',$smart['id'])));
if ($rs && $rs->RecordCount()) {
$taxes = array();
while (! $rs->EOF) {
@$taxes[$rs->fields['description']] += $rs->fields['amount'];
$rs->MoveNext();
}
$smart['tax_arr'] = array();
foreach ($taxes as $txds => $txamt)
array_push($smart['tax_arr'],array('description'=>$txds,'amount'=>$txamt));
}
}
# Get the discount details
if (! empty($smart['discount_amt'])) {
$rs = $db->Execute(sqlSelect('invoice_item_discount','amount,discount',array('where'=>array('invoice_id'=>$smart['id']))));
if ($rs && $rs->RecordCount()) {
$discounts = array();
while (! $rs->EOF) {
@$discounts[$rs->fields['discount']] += $rs->fields["amount"];
$rs->MoveNext();
}
$dhtml = '';
foreach ($discounts as $dsds => $dsamt)
$dhtml .= sprintf('<a href=\'?_page=core:search&module=discount&discount_name=%s\'>%s</a> - <br/>',$dsds,$dsds,number_format($dsamt,2));
$smart['discount_popup'] = $dhtml;
$dhtml = '';
foreach ($discounts as $dsds=>$dsamt)
$dhtml .= sprintf('%s - %s<br/>',$dsds,number_format($dsamt,2));
$smart['discount_popup_user'] = $dhtml;
}
}
# Get the checkout plugin details
if (! empty($smart['checkout_plugin_data'])) {
$plugin_data = unserialize($smart['checkout_plugin_data']);
if (is_array($plugin_data))
$smart['checkout_plugin_data'] = $plugin_data;
else
$smart['checkout_plugin_data'] = array($smart['checkout_plugin_data']);
}
# Get the term dates
include_once(PATH_MODULES.'net_term/net_term.inc.php');
$net_term = new net_term;
$smart['termdates'] = $net_term->getTermDates($smart['net_term_id'],$smart['date_orig'],$smart['due_date']);
# Get the line items
$rs = $db->Execute(sqlSelect('invoice_item','*',array('where'=>array('invoice_id'=>$smart['id']))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
$ii=0;
while (! $rs->EOF) {
$smart_items[$ii] = $rs->fields;
# Get the product attribs
if (! empty($rs->fields['product_attr'])) {
@$attrib = explode("\r\n",$rs->fields['product_attr']);
$js = '';
for ($attr_i=0; $attr_i<count($attrib); $attr_i++) {
$attributei = explode('==',$attrib[$attr_i]);
if (! empty($attributei[0]) && ! empty($attributei[1]))
$js .= sprintf('<div style="text-decoration: underline;">%s</div> : %s <br/>',$attributei[0],$attributei[1]);
}
$smart_items[$ii]['attribute_popup'] = $js;
}
# Get the date range if set
if (! empty($rs->fields['date_start']) && ! empty($rs->fields['date_stop'])) {
$C_translate->value('invoice','start',date(UNIX_DATE_FORMAT,$rs->fields['date_start']));
$C_translate->value('invoice','stop',date(UNIX_DATE_FORMAT,$rs->fields['date_stop']));
$smart_items[$ii]['range'] = $C_translate->translate('recur_date_range','invoice','');
}
# Set charge type for payment option list
$any_new = true;
if ($rs->fields['price_type']=='1' && ! empty($smart['recurr_arr']) && is_array(unserialize($smart['recurr_arr'])))
$any_recurring = true;
$rs->MoveNext();
$ii++;
}
# Create a summary (for duplicate skus w/identical price,and attributes, roll into a single value
if ($this->summarizeInvoice)
$smart_items = $this->sInvoiceItemsSummary();
# Get the checkout (payment) options
if ($VAR['_page'] != 'invoice:view') {
# Get the converted amount due:
if ($smart['billed_currency_id'] != $smart['actual_billed_currency_id']) {
global $C_list;
$CURRENCY = $smart['actual_billed_currency_id'];
if ($smart['billed_amt'] <= 0)
$total = $C_list->format_currency_decimal($smart['total_amt'],$CURRENCY);
else
$total = $C_list->format_currency_decimal($smart['total_amt'],$CURRENCY)-$smart['actual_billed_amt'];
} else {
$CURRENCY = $smart['billed_currency_id'];
$total = $smart['total_amt']-$smart['billed_amt'];
}
$q = sqlSelect('checkout','*',array('where'=>array('active'=>'1')));
if ($any_trial)
$q .= ' AND allow_trial=1';
if ($any_recurring)
$q .= ' AND allow_recurring=1';
if ($any_new)
$q .= ' AND allow_new=1';
$rs = $db->Execute($q);
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
if ($rs->RecordCount()) {
while (! $rs->EOF) {
$show = true;
# Check that the cart total is not to high:
if ($rs->fields['total_maximum'] != '' && $smart['total_amt'] >= $rs->fields['total_maximum'])
$show = false;
# Check that the cart total is not to low:
if ($rs->fields['total_miniumum'] != '' && $smart['total_amt'] <= $rs->fields['total_miniumum'])
$show = false;
# Check that the group requirement is met:
if ($show && ! empty($rs->fields['required_groups'])) {
global $C_auth;
$arr = unserialize($rs->fields['required_groups']);
if (count($arr) > 0 && ! empty($arr[0]))
$show = false;
for ($i=0; $i<count($arr); $i++) {
if ($C_auth->auth_group_by_id($arr)) {
$show = true;
$i = count($arr);
}
}
}
# Check that the customer is not ordering a blocked SKU
if ($show && ! empty($rs->fields['excluded_products'])) {
$arr = unserialize($rs->fields['excluded_products']);
if (count($arr) > 0) {
for ($i=0; $i<count($smart_items); $i++) {
for ($isk=0; $isk<count($arr); $isk++) {
if ($smart_items['product_id'] == $arr[$isk] && !empty($arr[$isk]) && !empty($smart_items['product_id'])) {
$show = false;
$i = count($smart);
$isk = count($arr);
}
}
}
}
}
$list_ord = 100;
if ($show) {
# Check if this method should be the default method
# By Amount
if (! empty($rs->fields['default_when_amount'])) {
$arr = unserialize($rs->fields['default_when_amount']);
for ($idx=0; $idx<count($arr); $idx++) {
if ($total >= $arr[$idx])
$list_ord--;
$idx = count($arr);
}
}
# By Currency
if (! empty($rs->fields['default_when_currency'])) {
$arr = unserialize($rs->fields['default_when_currency']);
for ($idx=0; $idx<count($arr); $idx++) {
if ($CURRENCY == $arr[$idx])
$list_ord--;
$idx = count($arr);
}
}
# By Group
if (! empty($rs->fields['default_when_group'])) {
$arr = unserialize($rs->fields['default_when_group']);
global $C_auth;
for ($idx=0; $idx<count($arr); $idx++) {
if ($C_auth->auth_group_by_id($arr[$idx]))
$list_ord--;
$idx = count($arr);
}
}
# By Country
if (! empty($rs->fields['default_when_country'])) {
$arr = unserialize($rs->fields['default_when_country']);
for ($idx=0; $idx<count($arr); $idx++) {
if ($account->fields['country_id'] == $arr[$idx])
$list_ord--;
$idx = count($arr);
}
}
# Add to the array
$checkout_optionsx[] = array('sort'=>$list_ord,'fields'=>$rs->fields);
}
$rs->MoveNext();
}
# Sort the checkout_options array by the [fields] element
if (count($checkout_optionsx)>0) {
foreach ($checkout_optionsx as $key => $row)
$sort[$key] = $row['sort'];
array_multisort($sort,SORT_ASC,$checkout_optionsx);
}
}
}
# Get the payment details
if ($C_list->is_installed('payment')) {
require_once(PATH_MODULES.'payment/payment.inc.php');
require_once(PATH_MODULES.'payment_item/payment_item.inc.php');
$pii = new payment_item();
$i=0;
foreach ($pii->sql_GetRecords(array('where'=>array('invoice_id'=>$VAR['id']),'orderby'=>'date_last,invoice_id')) as $payment) {
if ($payment['alloc_amt']) {
$pi = new payment($payment['payment_id']);
$smart['payment_data'][$i]['payment_id'] = $payment['payment_id'];
$smart['payment_data'][$i]['date_payment'] = $pi->getRecordAttr('date_payment');
$smart['payment_data'][$i]['total'] = $pi->getRecordAttr('total_amt');
$smart['payment_data'][$i]['alloc'] = $payment['alloc_amt'];
$i++;
}
}
}
# No results
if (count($smart) == 0) {
global $C_debug;
$C_debug->error(__FILE__, __METHOD__,'The selected record does not exist any longer, or your account is not authorized to view it');
return;
}
# Define the DB vars as a Smarty accessible block
global $smarty;
# Define the results
$smarty->assign('cart',$smart_items);
$smarty->assign('record',$smart);
$smarty->assign('checkoutoptions',$checkout_optionsx);
}
}
public function user_search_show($VAR) {
global $smarty;
$smart = parent::user_search_show($VAR);
# Add the balance to the array
foreach ($smart as $index => $details)
$smart[$index]['balance'] = $details['total_amt']-$details['billed_amt']-$details['credit_amt'];
$smarty->assign('search_show',$smart);
}
/**
* User view an invoice
*/
public function user_view($VAR) {
global $C_auth;
if (! SESS_LOGGED)
return false;
# Verify the account_id for this order is the SESS_ACCOUNT
if ($C_auth->auth_method_by_name('invoice','view') == false) {
$invoices = $this->sql_GetRecords(array('where'=>array('id'=>$VAR['id'])));
if (! count($invoices) || $invoices[0]['account_id'] != SESS_ACCOUNT)
return false;
}
$this->view($VAR);
}
/**
* Get the balance of the account
*/
public function sPreviousBalance() {
static $CACHE = array();
$id = $this->getRecordAttr('id');
if (! isset($CACHE[$id])) {
$CACHE[$id] = 0;
foreach ($this->sPreviousInvoices() as $item)
$CACHE[$id] += round($item['total_amt']-$item['billed_amt']-$item['credit_amt'],2);
}
return $CACHE[$id];
}
/**
* Get all the previous invoices still unpaid
*/
private function sPreviousInvoices() {
static $CACHE = array();
$id = $this->getRecordAttr('id');
if (! isset($CACHE[$id])) {
$CACHE[$id] = $this->sql_GetRecords(
array('where'=>sprintf('account_id=%s AND status=1 AND (billed_amt+IFNULL(credit_amt,0)<total_amt OR billed_amt IS NULL) AND date_orig<%s AND id!=%s',
$this->getRecordAttr('account_id'),$this->getRecordAttr('due_date'),$id)));
}
return $CACHE[$id];
}
/**
* Get the items on an invoice
*
* @uses invoice_item
*/
private function sInvoiceItems() {
static $CACHE = array();
$id = $this->getRecordAttr('id');
if (! isset($CACHE[$id])) {
include_once(PATH_MODULES.'invoice_item/invoice_item.inc.php');
$ito = new invoice_item();
$CACHE[$id] = $ito->sInvoiceItems($this->getRecordAttr('id'));
}
return $CACHE[$id];
}
/**
* Create modified array for invoice summarization
*
* This function will summarise the invoice items based on the same
* SKU, BASE_PRICE, SETUP_PRICE & PRODUCT_ATTR
*/
public function sInvoiceItemsSummary() {
$sum = array();
foreach ($this->sInvoiceItems() as $index => $item) {
$unique = true;
# Unique line item
if (isset($sum[$item['sku']])) {
# Is unique price/attributes?
foreach ($sum[$item['sku']] as $sid => $flds) {
if ($flds['price_base'] == $item['price_base'] &&
$flds['price_setup'] == $item['price_setup']) {
$sum[$item['sku']][$sid]['quantity'] += $item['quantity'];
$unique = false;
break;
}
}
}
# Unique line item
if ($unique) {
$a = count($sum[$item['sku']]);
$sum[$item['sku']][$a] = $item;
$sum[$item['sku']][$a]['product_name'] = $this->sLineItemDesc($index,true);
}
}
if (count($sum)) {
$items = array();
foreach ($sum as $sku => $item)
foreach ($item as $sitem)
array_push($items,$sitem);
return $items;
}
}
/**
* Return a line description, suitable for printing on invoices
*
* @uses product_translate
*/
private function sLineItemDesc($id,$summary=false) {
$li = $this->sInvoiceItems();
if (! isset($li[$id]))
return 'Other Item';
require_once(PATH_MODULES.'product_translate/product_translate.inc.php');
$pdo = new product_translate($li[$id]['product_id']);
if ($summary) {
if (is_null($li[$id]['sku']) && is_null($li[$id]['product_id']))
return _('Other Item');
elseif (is_null($li[$id]['product_id'])) {
switch ($li[$id]['sku']) {
case 'DOMAIN-REGISTER': $name = _('Register Domain'); break;
case 'DOMAIN-TRANSFER': $name = _('Transfer Domain'); break;
case 'DOMAIN-PARK': $name = _('Park Domain'); break;
case 'DOMAIN-RENEW': $name = _('Renew Domain'); break;
default: $name = $sku;
}
return $name;
} elseif ($li[$id]['product_id'])
return $pdo->getRecordAttr('name');
} else {
switch ($li[$id]['sku']) {
case 'DOMAIN-REGISTER': $name = _('Register Domain'); break;
case 'DOMAIN-TRANSFER': $name = _('Transfer Domain'); break;
case 'DOMAIN-PARK': $name = _('Park Domain'); break;
case 'DOMAIN-RENEW': $name = _('Renew Domain'); break;
default: return $li[$id]['product_name'] ? $li[$id]['product_name'] :
($pdo->getRecordAttr('description_short') ? $pdo->getRecordAttr('description_short') : $pdo->getRecordAttr('name'));
}
return $li[$id]['product_name'] ? $li[$id]['product_name'] : sprintf('%s (%s.%s)',$name,$li[$id]['domain_name'],$li[$id]['domain_tld']);
}
return 'Other Item';
}
public function sql_invoice_soon($fields=null,$adddays=0,$account=null,$orderby=null) {
return $this->sql_InvoiceSoon($fields,$adddays,$account,$orderby);
}
/**
* Return the SQL that determines which invoices need to be generated
*
* Invoices are generated when the greater of:
* + system default (setup:max_inv_gen_period), (to be deprecated)
* + system default (setup_invoice:invoice_advance_gen),
* + the account setting (account:invoice_advance_gen)
* + the net_terms setting (net_term:invoice_advance_gen) (linked from the account:net_term_id))
*
* This function can be called to display the SQL that is need to generate upcoming invoices.
*
* @param string The SQL to use in the SELECT portion of the query, to return the fields
* @param int Days in advance of the normal invoice generation date to obtain
* @param int|array Limit the query to just a(n) (set of) Account ID(s)
* @param string The SQL to use in the ORDER BY portion of the query.
*/
public function sql_InvoiceSoon($fields=null,$adddays=0,$account=null,$orderby=null) {
global $C_list;
# Get the max invoice days from the system configuration tables.
$days = $this->sInvoiceDays();
# Pre-notification date for service
$max_date = date('Y-m-d',time()+(($adddays+$days)*86400));
if ($account) {
if (is_array($account))
$account_where = sprintf('AND account.id IN (%s)',implode(',',$account));
else
$account_where = sprintf('AND account.id=%s',$account);
} else
$account_where = '';
if (is_null($fields))
$fields = 'DISTINCT service.id AS sid,account.id AS account_id,invoice.id AS iid,FROM_UNIXTIME(service.date_next_invoice,\'%Y-%m-%d\') AS invoice_date,service.sku AS sku,service.price AS price,account.first_name AS first_name,account.last_name AS last_name,account.currency_id AS billed_currency_id,service.date_orig AS date_orig';
if (is_null($orderby))
$orderby = 'ORDER BY account_id,invoice_date,sid';
else
$orderby = sprintf('ORDER BY %s',$orderby);
// @todo NET_TERM is not tested.
if ($C_list->is_installed('net_term')) {
$net_term = 'LEFT JOIN {p}net_term AS net_term ON (account.net_term_id=net_term.id AND net_term.site_id={s})';
$net_term_where = sprintf('OR ((net_term.invoice_advance_gen!="" OR net_term.invoice_advance_gen IS NOT NULL) AND service.date_next_invoice<=((86400*(net_term.invoice_advance_gen+%s+%s))+(UNIX_TIMESTAMP(CURDATE()))))',$adddays,$days);
} else {
$net_term = '';
$net_term_where = '';
}
$sql = sprintf('
SELECT %s
FROM {p}service AS service
JOIN {p}account AS account ON (service.account_id=account.id AND account.site_id={s})
LEFT JOIN {p}invoice AS invoice ON (service.invoice_id=invoice.id AND invoice.site_id={s})
%s
WHERE service.site_id={s}
AND service.active=1
AND price > 0
AND (service.suspend_billing IS NULL OR service.suspend_billing=0)
AND (service.date_next_invoice>0 AND service.date_next_invoice IS NOT NULL)
AND (
((account.invoice_advance_gen!="" OR account.invoice_advance_gen IS NOT NULL) AND service.date_next_invoice<=((86400*(account.invoice_advance_gen+%s+%s))+(UNIX_TIMESTAMP(CURDATE()))))
%s
OR service.date_next_invoice<=UNIX_TIMESTAMP("%s")
) %s %s',
$fields,$net_term,$adddays,$days,$net_term_where,$max_date,$account_where,$orderby);
$sql = str_replace('{p}',AGILE_DB_PREFIX,$sql);
$sql = str_replace('{s}',DEFAULT_SITE,$sql);
return $sql;
}
/**
* Template method to list all invoices that will be generated soon
*/
public function tmInvoiceSoon($VAR) {
global $smarty,$C_list;
$db = &DB();
$order_by = isset($VAR['order_by']) ? $VAR['order_by'] : 'account_id,invoice_date,sid';
# Then from the setup_invoice table.
$setup = $db->Execute(sqlSelect('setup_invoice','advance_notice'));
# Run the database query
$result = $db->Execute($this->sql_InvoiceSoon(null,$setup->fields['advance_notice'],null,$order_by));
# Error reporting
if (! $result) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
} elseif (! $result->RecordCount()) {
return false;
}
$invoice = array();
$i = 0;
while (! $result->EOF) {
$result->fields['_C'] = ++$i%2 ? 'row1' : 'row2';
$result->fields['id'] = $result->fields['sid'];
array_push($invoice,$result->fields);
$result->MoveNext();
}
# Create the search record:
if (count($invoice) > 0) {
# create the search record
#include_once(PATH_CORE.'search.inc.php');
#$search = new CORE_search;
#$arr['module'] = $this->module;
#$arr['sql'] = $this->sql_InvoiceSoon();
#$arr['limit'] = $limit;
#$arr['order_by']= $order_by;
#$arr['results'] = $results;
#$search->add($arr);
# Define the search id and other parameters for Smarty
$smarty->assign('search_id',$search->id);
$smarty->assign('page','1');
$smarty->assign('order_by',$order_by);
}
# Define the result count
$smarty->assign('results',count($invoice));
$smarty->assign('search_show',$invoice);
}
public function performance($VAR) { return $this->tmPerformance($VAR); }
/**
* Site Performance (for the admin dashboard)
*/
public function tmPerformance($VAR) {
global $smarty,$C_list,$C_translate;
$db = &DB();
$period = array();
# Get the period type, default to month
$period['period'] = empty($VAR['period']) ? 'y' : $VAR['period'];
switch ($period['period']) {
case 'w':
$smarty->assign('period_compare',sprintf('%s %s %s',_('This Week'),_('vs'),('Last Week')));
$smarty->assign('period_forcast',_('This Week'));
$period['this_start'] = mktime(0,0,0,date('m'),date('d')-date('w'),date('y'));
$period['this_end'] = mktime(23,59,59,date('m'),date('d'),date('y'));
$period['last_start'] = mktime(0,0,0,date('m'),date('d',$period['this_start'])-7,date('y'));
$period['last_end'] = mktime(23,59,59,date('m'),date('d')-7,date('y'));
break;
case 'm':
$smarty->assign('period_compare',sprintf('%s %s %s',_('This Month'),_('vs'),('Last Month')));
$smarty->assign('period_forcast',_('This Month'));
$period['this_start'] = mktime(0,0,0,date('m'),1,date('y'));
$period['this_end'] = mktime(23,59,59,date('m'),date('d'),date('y'));
$period['last_start'] = mktime(0,0,0,date('m',$period['this_start'])-1,1,date('y'));
$period['last_end'] = mktime(23,59,59,date('m')-1,date('d'),date('y'));
break;
case 'y':
default:
$smarty->assign('period_compare',sprintf('%s %s %s',_('This Year'),_('vs'),('Last Year')));
$smarty->assign('period_forcast',_('This Year'));
$period['this_start'] = mktime(0,0,0,1,1,date('y',time()));
$period['this_end'] = mktime(23,59,59,date('m'),date('d'),date('y'));
$period['last_start'] = mktime(0,0,0,1,1,date('y',$period['this_start'])-1);
$period['last_end'] = mktime(23,59,59,date('m'),date('d'),date('y')-1);
break;
}
# Get sales for this period
$rs = $db->Execute(sqlSelect('invoice','SUM(total_amt) as total_amt',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s',$period['this_start'],$period['this_end']))));
if ($rs && $rs->RecordCount())
$this_amt = $rs->fields['total_amt'];
else
$this_amt = 0;
# Get sales for last period
$rs = $db->Execute(sqlSelect('invoice','SUM(total_amt) as total_amt',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s',$period['last_start'],$period['last_end']))));
if ($rs && $rs->RecordCount())
$last_amt = $rs->fields['total_amt'];
else
$last_amt = 0;
$smarty->assign('sales_current',$this_amt);
$smarty->assign('sales_previous',$last_amt);
$smarty->assign('sales_change',($last_amt > 0) ? $this_amt/$last_amt*100-100 : 0);
# Get forcast for current period
switch ($period['period']) {
case 'w':
$dow = date('w')+1;
$forcast_daily = $this_amt/$dow;
$forcast_l_daily = $last_amt/7;
$forcast_change = $forcast_daily/$forcast_1_daily*100-100;
$forcast_current = $forcast_daily*7;
break;
case 'm':
$forcast_daily = $this_amt/date('d');
$forcast_1_daily = $last_amt/date('t',mktime(0,0,0,date('m')-1,1,date('y')));
$forcast_change = $forcast_daily/$forcast_1_daily*100-100;
$forcast_current = $forcast_daily*date('t');
break;
case 'y':
default:
$forcast_daily = $this_amt/date('z');
$forcast_1_daily = $last_amt/356;
$forcast_change = $forcast_daily/$forcast_1_daily*100-100;
$forcast_current = $forcast_daily*365;
break;
}
$smarty->assign('forcast_current',$forcast_current);
$smarty->assign('quota_current',$forcast_daily);
$smarty->assign('forcast_change',($last_amt > 0) ? $forcast_daily/$forcast_1_daily*100 : 0);
# Get AR credits for this period
$rs = $db->Execute(sqlSelect('invoice','SUM(billed_amt) as total_amt',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s AND billed_amt>0',$period['this_start'],$period['this_end']))));
if ($rs && $rs->RecordCount())
$this_billed_amt = $rs->fields['total_amt'];
else
$this_billed_amt = 0;
# Get AR credits for last period
$rs = $db->Execute(sqlSelect('invoice','SUM(billed_amt) as total_amt',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s AND billed_amt>0',$period['last_start'],$period['last_end']))));
if ($rs && $rs->RecordCount())
$last_billed_amt = $rs->fields['total_amt'];
else
$last_billed_amt = 0;
$smarty->assign('ar_credits_current',$this_billed_amt);
$smarty->assign('ar_credits_previous',$last_billed_amt);
$smarty->assign('ar_credit_change',($last_billed_amt > 0) ? $this_billed_amt/$last_billed_amt*100-100 : 0);
# Get AR Balance
$smarty->assign('ar_balance_current',$this_billed_amt-$this_amt);
$smarty->assign('ar_balance_last',$last_billed_amt-$last_amt);
# Get Users (current)
$rs = $db->Execute(sqlSelect('account','COUNT(*) as count',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s',$period['this_start'],$period['this_end']))));
if ($rs && $rs->RecordCount())
$users_current = $rs->fields['count'];
else
$users_current = 0;
# Get Users (previous)
$rs = $db->Execute(sqlSelect('account','COUNT(*) as count',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s',$period['last_start'],$period['last_end']))));
if ($rs && $rs->RecordCount())
$users_previous = $rs->fields['count'];
else
$user_previous = 0;
$smarty->assign('users_current',$users_current);
$smarty->assign('users_previous',$users_previous);
$smarty->assign('users_change',($users_previous > 0) ? $users_current/$users_current*100-100 : 0);
# Get Affiliate stats
if ($C_list->is_installed('affiliate')) {
$smarty->assign('show_affiliates',true);
# Get affiliate sales for this period
$rs = $db->Execute(sqlSelect('invoice','SUM(total_amt) as total_amt',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s AND affiliate_id NOT IN (0,"")',$period['this_start'],$period['this_end']))));
if ($rs && $rs->RecordCount())
$this_amt = $rs->fields['total_amt'];
else
$this_amt = 0;
# Get affiliate sales for last period
$rs = $db->Execute(sqlSelect('invoice','SUM(total_amt) as total_amt',
array('where'=>sprintf('date_orig>=%s AND date_orig<=%s AND affiliate_id NOT IN (0,"")',$period['last_start'],$period['last_end']))));
if ($rs && $rs->RecordCount())
$last_amt = $rs->fields['total_amt'];
else
$last_amt = 0;
$smarty->assign('affiliate_sales_current',$this_amt);
$smarty->assign('affiliate_sales_previous',$last_amt);
$smarty->assign('affiliate_sales_change',($last_amt > 0) ? $this_amt/$last_amt*100-100 : 0);
}
# Generate the Calendar Overview
include_once(PATH_MODULES.'core/calendar.inc.php');
$calendar = new calendar;
$C_list->currency(DEFAULT_CURRENCY);
$currency_symbol = $C_list->format_currency[DEFAULT_CURRENCY]['symbol'];
# Get the paid/due invoice statistics
$records = $this->sql_GetRecords(array('where'=>sprintf('date_orig>=%s AND date_orig<=%s',$calendar->start,$calendar->end)));
$paid = array();
$due = array();
if (count($records)) {
foreach ($records as $rs) {
$day = date('j',$rs['date_orig']);
if ($rs['billed_amt'] > 0 && ($rs['billing_status'] == 1 || $rs['refund_status'] != 1))
@$paid[$day] += $rs['billed_amt'];
if ($rs['billing_status'] != 1 && $rs['refund_status'] != 1)
@$due[$day] += $rs['total_amt']-$rs['billed_amt'];
}
foreach ($paid as $day => $item)
$calendar->add(sprintf('<b>%s</b> - %s%s',_('Paid'),$currency_symbol,number_format($item,2)),$day,'green','green');
foreach ($due as $day => $item)
$calendar->add(sprintf('<b>%s</b> - %s%s',_('Due'),$currency_symbol,number_format($item,2)),$day,'red','red');
}
# Get the upcoming due services
$rs = $db->Execute(sqlSelect('service','date_next_invoice,price',
array('where'=>sprintf('price>0 AND date_next_invoice>=%s AND date_next_invoice<=%s AND suspend_billing<>1',
$calendar->start,$calendar->end))));
if ($rs && $rs->RecordCount()) {
$due = array();
while (! $rs->EOF) {
$day = date('j',$rs->fields['date_next_invoice']);
@$due[$day] += $rs->fields['price'];
$rs->MoveNext();
}
foreach ($due as $day=>$item)
$calendar->add(sprintf('<b>%s</b> - %s%s',_('Recurring'),$currency_symbol,number_format($item,2)),$day,'grey','grey');
}
$smarty->assign('calendar',$calendar->generate());
return;
}
/**
* Task based function to e-mail or store printable PDF of all unprinted invoices
*
* @todo This seems to be hard limited to do 100 invoices in a run - why? (make it configurable, or remove the limit?)
*/
public function task_DeliverInvoices() {
# Get all unprinted invoices
$db = &DB();
$rs = $db->SelectLimit(sqlSelect($db,array('invoice','account'),
'A.id,B.email,B.first_name,B.last_name,B.invoice_delivery,B.invoice_show_itemized',
'(A.billing_status=0 OR A.billing_status IS NULL) AND (A.print_status=0 OR A.print_status=NULL) AND (A.status=1) AND A.account_id=B.id AND (B.invoice_delivery IS NOT NULL AND B.invoice_delivery>0)'),100);
if ($rs && $rs->RecordCount()) {
# Send the e-mail....
require_once(PATH_INCLUDES.'phpmailer/class.phpmailer.php');
$mail = new PHPMailer();
$mail->From = SITE_EMAIL;
$mail->FromName = SITE_NAME;
/*
$mail->SMTPAuth = true;
$mail->Host = "smtp.domain.com";
$mail->Username = "user";
$mail->Password = "pass";
$mail->Mailer = "smtp";
$mail->Debug = true;
*/
while (! $rs->EOF) {
$this->sql_LoadRecord($rs->fields['id']);
switch ($rs->fields['invoice_delivery']) {
# Email Invoice
case 1:
if ($file = $this->pdf(null,null,array('dest'=>'S'))) {
$mail->AddAddress($rs->fields['email'], sprintf('%s %s',$rs->fields['first_name'],$rs->fields['last_name']));
$mail->AddBcc('deon@leenooks.vpn');
$mail->AddBcc('chris@graytech.com.au');
$mail->Subject = sprintf('%s %s: %s',SITE_NAME,_('Invoice'),$this->getRecordAttr('id'));
$mail->AltBody = sprintf("Please find the printable version of invoice number %s attached.\r\n\r\nThank you,\r\n%s",$this->getRecordAttr('id'),SITE_NAME);
$mail->IsHTML(true);
$mail->AddEmbeddedImage(sprintf('%s/%s',PATH_THEMES.DEFAULT_THEME,'invoice/invoice-logo.png'),'logoimg','','base64','image/png');
$mail->Body = sprintf('<p>Please find attached invoice <b>%s</b> from %s</p>',$this->getRecordAttr('id'),SITE_NAME);
$mail->Body .= sprintf('<p>A PDF version is also attached with more detail and payment options. Alternatively, you can visit our website and <a href="%s?_page=invoice:user_view&id=%s">pay online</a>.</p>',
URL,$this->getRecordAttr('id'));
$mail->Body .= "<hr/>\n";
$mail->Body .= wordwrap($this->html(array('id'=>$this->getRecordAttr('id'))));
$mail->AddStringAttachment($file,sprintf('%s.pdf',$this->getPrintInvoiceID()),'base64','application/pdf');
if ($mail->Send())
$db->Execute(sqlUpdate($db,'invoice',array('print_status'=>1),array('id'=>$this->getRecordAttr('id'))));
else
printf('Unable to email invoice # %s to %s<br/>',$this->getRecordAttr('id'),$rs->fields['email']);
$mail->ClearAddresses();
$mail->ClearAttachments();
}
break;
# Print Invoice
case 2:
$file = tempnam(PATH_FILES,sprintf('pdf_inv_%s.pdf',$this->getPrintInvoiceID()));
$this->pdf(null,null,array('dest'=>'F','file'=>$file));
if (copy($file,sprintf('%sinvoice_%s.pdf',AGILE_PDF_INVOICE_PATH,$this->getPrintInvoiceID())))
$db->Execute(sqlUpdate($db,'invoice',array('print_status'=>1),array('id'=>$this->getRecordAttr('id'))));
unlink($file);
break;
default:
printf('Unknown invoice_delivery: %s for %s<br/>',$rs->fields['invoice_delivery'],$this->getRecordAttr('id'));
}
$rs->MoveNext();
}
}
}
/**
* Email a list of overdue invoices.
*
* @uses PHPMailer
* @uses staff
*/
public function task_OverdueListEmail() {
# @todo Make this configurable somewhere.
$receipient_dep = 'Accounts';
require_once(PATH_MODULES.'staff/staff.inc.php');
$so = new staff;
if (! $staff = $so->sDepartmentMemberEmail($receipient_dep))
return false;
$db = &DB();
$rs = $db->Execute(sqlSelect($db,array('invoice','account'),'A.id,A.account_id,ROUND(SUM(A.total_amt-A.billed_amt-IFNULL(A.credit_amt,0)),2) as total,B.first_name,B.last_name,A.due_date',sprintf('A.status=1 AND ROUND(A.total_amt-A.billed_amt-IFNULL(A.credit_amt,0),2)>0 AND A.account_id=B.id AND A.due_date<%s',time()),false,false,false,'account_id,id'));
if ($rs && $rs->RecordCount()) {
$body = '';
$account_total = 0;
$account_id = '';
$i = 0;
$count = 0;
$grand_total = 0;
while (! $rs->EOF) {
if ($account_id != $rs->fields['account_id'] && $i) {
$body .= sprintf("Total: %3.2f\n\n",$account_total);
$account_total = 0;
$i = 0;
}
if (! $i)
$body .= sprintf("%s %s (%s)\n",$rs->fields['last_name'],$rs->fields['first_name'],$rs->fields['account_id']);
$body .= sprintf(" Invoice: %s, Due Date: %s, Amount: %3.2f\n",$rs->fields['id'],date('Y-m-d',$rs->fields['due_date']),$rs->fields['total']);
$account_total += $rs->fields['total'];
$grand_total += $rs->fields['total'];
$account_id = $rs->fields['account_id'];
$count++;
$i++;
$rs->MoveNext();
}
if ($account_total)
$body .= sprintf("Total: %3.2f\n",$account_total);
$body .= "\n";
if ($count || $ground_total)
$body .= sprintf("%3.2f outstanding in %s invoices\n",$grand_total,$count);
# Send the e-mail....
require_once(PATH_INCLUDES.'phpmailer/class.phpmailer.php');
$mail = new PHPMailer();
foreach ($staff as $email => $name)
$mail->AddAddress($email,$name);
$mail->From = SITE_EMAIL;
$mail->FromName = SITE_NAME;
$mail->Subject = _('List of Invoices Overdue');
$mail->Body = $body;
$mail->Send();
}
}
/**
* Return the invoice ID
*/
public function getPrintInvoiceID() {
return sprintf('%02s-%04s-%06s',DEFAULT_SITE,$this->getRecordAttr('account_id'),$this->getRecordAttr('id'));
}
public function getPrintInvoiceNum() {
return $this->getRecordAttr('id');
}
/**
* Add an item to the invoice
*
* @uses host_tld
* @uses invoice_item
* @uses product
*/
public function aaddItem($item) {
include_once(PATH_MODULES.'invoice_item/invoice_item.inc.php');
$ito = new invoice_item;
foreach ($item as $i => $v)
if (isset($ito->field[$i]))
$ito->setRecordAttr($i,$v);
$ito->setRecordAttr('invoice_id',$this->getRecordAttr('id'));
if (! is_null($this->getRecordAttr('account_id')))
$ito->setRecordAttr('account_id',$this->getRecordAttr('account_id'));
if (! empty($item['product_id'])) {
include_once(PATH_MODULES.'product/product.inc.php');
$po = new product($item['product_id']);
$ito->setRecordAttr('product_name',$po->getTranslateField('name'));
$ito->setRecordAttr('product_id',$po->getRecordAttr('id'));
// $ito->setRecordAttr('sku',$po->getRecordAttr('sku'));
$ito->setRecordAttr('quantity',isset($item['quantity']) ? $item['quantity'] : 1);
$ito->setRecordAttr('price_type',$po->getRecordAttr('price_type'));
$ito->setRecordAttr('recurring_schedule',isset($item['recurring_schedule']) ? $item['recurring_schedule'] : $po->getRecordAttr('price_recurr_default'));
if (isset($item['price_base']))
$ito->setBaseRate($item['price_base']);
else {
$price = $po->price_prod(
array(
'price_type'=>$po->getRecordAttr('price_type'),
'price_recurr_type'=>$po->getRecordAttr('price_recurr_type'),
'price_recurr_weekday'=>$po->getRecordAttr('price_recurr_weekday'),
'price_recurr_week'=>$po->getRecordAttr('price_recurr_week'),
'price_group'=>$po->getRecordAttr('price_group'),
'price_base'=>$po->getRecordAttr('price_base'),
'price_setup'=>$po->getRecordAttr('price_setup')),
$ito->getRecordAttr('recurring_schedule'),$this->getRecordAttr('account_id'),false);
$ito->setRecordAttr('price_setup',isset($item['price_setup']) ? $item['price_setup'] : $price['setup']);
$ito->setBaseRate($price['base']);
}
$billdates = $po->recurrDates($ito->getRecordAttr('recurring_schedule'),$po->getRecordAttr('recur_weekday'),null,
is_null($ito->getRecordAttr('date_start')) ? time() : $ito->getRecordAttr('date_start'));
$ito->setRecordAttr('date_start',$billdates['date']);
$ito->setRecordAttr('date_stop',$billdates['end']);
$ito->setProRata($billdates['prorata']);
} elseif (! empty($item['charge_id'])) {
} elseif ($item['type'] == 'domain') {
include_once(PATH_MODULES.'host_tld/host_tld.inc.php');
$hto = new host_tld();
#@todo - TEMP until we have another recurrDates() function that we can use.
include_once(PATH_MODULES.'product/product.inc.php');
$po = new product();
$tld = $hto->sql_GetRecords(array('where'=>array('name'=>$item['domain_tld'])));
if (! count($tld)) { printf('NO TLD FOR %s??',$item['domain_tld']);die();};
$tld = array_pop($tld);
$pg = unserialize($tld['price_group']);
# @todo - need to improve this code after reworking host_tld
switch (@$item['host_type']) {
case 'register' :
$ito->setRecordAttr('product_name','Domain Name Register');
break;
default:
$ito->setRecordAttr('product_name','Domain Name Renewal');
}
$ito->setRecordAttr('recurring_schedule',isset($item['recurring_schedule']) ? $item['recurring_schedule'] : 5);// get this from host_tld
$billdates = $po->recurrDates($ito->getRecordAttr('recurring_schedule'),null,null,
is_null($ito->getRecordAttr('date_start')) ? time() : $ito->getRecordAttr('date_start'),true);
$ito->setRecordAttr('date_start',$billdates['date']);
$ito->setRecordAttr('date_stop',$billdates['end']);
$ito->setProRata($billdates['prorata']);
$ito->setRecordAttr('price_type',1);
$ito->setRecordAttr('price_setup',0);
if (is_null($ito->getRecordAttr('price'))) {
include_once(PATH_MODULES.'host_tld/host_tld.inc.php');
$tldObj = new host_tld;
$tldprice = $tldObj->price_tld_arr($ito->getRecordAttr('domain_tld'),$item['host_type'],
false,false,false,$ito->getRecordAttr('account_id'));
$ito->setBaseRate($tldprice[$ito->getRecordAttr('domain_term')]);
}
} else {
echo '<PRE>';print_r(array('i'=>$item,'ii'=>$ito));
echo '<B>NEED TO FIGURE OUT THE PRICE IF WE ARE NOT A PRODUCT</B>';die();
$ito->setRecordAttr('price_setup',0);
$ito->setRecordAttr('recurring_schedule',isset($item['recurr_schedule']) ? $item['recurr_schedule'] : 0);
}
# If we are a cart, we'll set a cart ID, so the item can be deleted.
if (isset($item['cart_id']))
$ito->setRecordAttr('cart_id',$item['cart_id']);
array_push($this->items,$ito);
# Return the item id.
return count($this->items)-1;
}
public function sCountItems() {
return count($this->items);
}
public function getItems() {
$items = array();
foreach ($this->items as $item)
array_push($items,$item->getRecord());
return $items;
}
public function getProductItems() {
$items = array();
foreach ($this->items as $item)
if (! is_null($item->getRecordAttr('product_id')))
array_push($items,$item->getRecordAttr('product_id'));
return $items;
}
public function getProductItemTypes() {
$items = array();
foreach ($this->items as $item)
if (! is_null($item->getRecordAttr('price_type')))
array_push($items,$item->getRecordAttr('price_type'));
return array_unique($items,SORT_NUMERIC);
}
/**
* This function will get all the discount codes used in the invoice
* it will also cause all the discounts to be re-calcated
*/
public function getDiscountDetails() {
$discounts = array();
foreach ($this->items as $item)
foreach ($item->getDiscountArr($this->sSubTotal()) as $discount)
@$discounts[$discount['discount']] += $discount['amount'];
$d = array();
foreach ($discounts as $k=>$v)
array_push($d,array('name'=>$k,'total'=>$v));
return $d;
}
public function sTotalDiscount($recalc=false) {
$total = 0;
if ($recalc)
$this->getDiscountDetails();
foreach ($this->items as $item)
$total += $item->getRecordAttr('discount_amt');
return $total;
}
# @todo change this to work the same way as getDiscountDetails()
public function getTaxDetails() {
$taxes = array();
foreach ($this->items as $item)
foreach ($item->getTaxArr() as $tax)
@$taxes[$tax['name']] += $tax['rate'];
return $taxes;
}
public function sTotalTax($recalc) {
$total = 0;
if ($recalc)
$this->getTaxDetails();
foreach ($this->items as $item)
$total += $item->getRecordAttr('tax_amt');
return $total;
}
/**
* This function will calculate the pre-tax/pre-discounted totals for this invoice
*/
public function sSubTotal($calc=false) {
static $total;
if ($total && ! $calc)
return $total;
else
$total = 0;
foreach ($this->items as $item)
$total += $item->sGetSubTotalAmt();
return $total;
}
public function sTotal($calc=false) {
static $total;
if ($total && ! $calc)
return $total;
else
$total = 0;
foreach ($this->items as $item)
$total += $item->sGetTotalAmt($calc);
return $total;
}
/**
* Calculate the recurring amount for this invoice
*
* The recurring amount is only applicable if all items have the same recur_schedule
*/
public function sRecurAmt($calc=false) {
static $total;
if (! is_null($total) && ! $calc)
return $total;
else
$total = null;
$sched = null;
foreach ($this->items as $item) {
if (is_null($sched))
$sched = $item->getRecordAttr('recurring_schedule');
if ($sched != $item->getRecordAttr('recurring_schedule'))
return null;
$total += $item->sGetRecurAmt();
}
return $total;
}
public function sql_SaveRecord($noconvert=false) {
global $VAR;
# (Re)Calculate our discounts and taxes
$this->setRecordAttr('discount_amt',$this->sTotalDiscount(true));
$this->setRecordAttr('tax_amt',$this->sTotalTax(true));
$this->setRecordAttr('total_amt',$this->sTotal(true));
# Save the invoice and items
# @todo make this into a transaction, so if the item records fail, we dont have a partial save
if ($id = parent::sql_SaveRecord($noconvert)) {
foreach ($this->items as $item) {
$item->setRecordAttr('invoice_id',$id);
# Remove the cart id
$item->delRecordAttr('cart_id');
if (! $item->sql_SaveRecord($noconvert,false)) {
echo '<PRE>';print_r($item);die();
}
}
}
return $id;
}
public function custom_tracking($VAR) { return $this->drCustomTracking($VAR); }
/**
* Custom Tracking
*/
public function drCustomTracking($VAR) {
# If we dont have a tracking file, or we are not logged in, no point continuing.
if (! is_file(PATH_FILES.'tracking.txt') || ! SESS_LOGGED)
return false;
# Check if we are in the iframe, otherwise render the iframe.
if (empty($VAR['_escape']) || empty($VAR['confirm'])) {
printf('<iframe id="custom_ecom_track" style="border:0px; width:0px; height:0px;" scrolling="auto" frameborder="0" src="?_page=core:blank&_escape=1&confirm=1&do[]=invoice:drCustomTracking&rand=%s"></iframe>',
md5(microtime()));
return;
}
# Get the un-tracked invoice details
$db = &DB();
$result = $this->sql_GetRecords(array('where'=>sprintf('(custom_affiliate_status IS NULL OR custom_affiliate_status=0) AND billing_status=1 AND account_id=%s',SESS_ACCOUNT)));
if (! count($result))
return false;
# Get the totals
$invoice = '';
$total_amount = 0;
foreach ($result as $record) {
if (! empty($invoice))
$invoice .= '-';
$invoice .= $record['id'];
$total_amount += $record['total_amt'];
}
# Echo the custom tracking code to the screen:
$tracking = file_get_contents(PATH_FILES.'tracking.txt');
$tracking = str_replace('%%amount%%',$total_amount,$tracking);
$tracking = str_replace('%%invoice%%',$invoice,$tracking);
$tracking = str_replace('%%affiliate%%',SESS_AFFILIATE,$tracking);
$tracking = str_replace('%%campaign%%',SESS_CAMPAIGN,$tracking);
$tracking = str_replace('%%account%%',SESS_ACCOUNT,$tracking);
echo $tracking;
# Update the record so it is not tracked again
$rs = $db->Execute(
sqlUpdate('invoice',array('custom_affiliate_status'=>1),array('where'=>sprintf('account_id=%s AND billing_status=1',SESS_ACCOUNT))));
if ($rs === false) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,sprintf('%s (%s)',$db->ErrorMsg(),$sql));
}
return true;
}
public function autoApproveInvoice($id) { return $this->pAutoApprove($id); }
/**
* Auto approve Invoice
*/
public function pAutoApprove($id) {
$db = &DB();
$do = false;
# Get the invoice details
$invoices = $this->sql_GetRecords(array('where'=>array('id'=>$id,'process_status'=>0)));
if (! count($invoices))
return false;
$invoice = array_pop($invoices);
# Get the checkout details
$checkout = $db->Execute(sqlSelect('checkout','*',array('where'=>array('id'=>$invoice['checkout_plugin_id']))));
if (! $checkout) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Get the account details
$account = $db->Execute(sqlSelect('account','*',array('where'=>array('id'=>$invoice['account_id']))));
if ($account) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Is this a recurring invoices, and is manual approvale req?
if ($invoice['type'] == 1 && $checkout->fields['manual_approval_recur'] != 1)
$do = true;
# Manual approval required for all?
if ($invoice['type'] != 1 && $checkout->fields['manual_approval_all'] != 1)
$do = true;
if (! $do) {
# Manual approval required for invoice amount?
if (! empty($checkout->fields['manual_approval_amount']) && $do == true)
if ($checkout->fields['manual_approval_amount'] <= $invoice['total_amt'])
$do = false;
# Manual approval required for user's country?
if (! empty($checkout->fields['manual_approval_country']) && $do == true) {
$arr = unserialize($checkout->fields['manual_approval_country']);
for ($i=0; $i<count($arr); $i++)
if ($account->fields['country_id'] == $arr[$i])
$do = false;
}
# Manual approval req. for user's currency?
if (! empty($checkout->fields['manual_approval_currency']) && $do == true) {
$arr = unserialize($checkout->fields['manual_approval_currency']);
for ($i=0; $i<count($arr); $i++)
if ($invoice['actual_billed_currency_id'] == $arr[$i])
$do = false;
}
# Manual approval required for user's group(s)?
if (! empty($checkout->fields['manual_approval_group']) && $do == true) {
# Get the group details
$groups = $db->Execute(sqlSelect('account_group','group_id',array('where'=>array('account_id'=>$invoice['account_id'],'active'=>1))));
if (! $groups) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
$arr = unserialize($checkout->fields['manual_approval_group']);
while (! $groups->EOF) {
for ($i=0; $i<count($arr); $i++) {
$idx = $groups->fields['group_id'];
if ($idx == $arr[$i])
$do = false;
}
$groups->MoveNext();
}
}
}
# Approve the invoice
if ($do)
$this->pApprove($id);
else {
# Admin manual approval notice
include_once(PATH_MODULES.'email_template/email_template.inc.php');
$mail = new email_template;
$mail->send('invoice_manual_auth_admin',$invoice['account_id'],$invoice['id'],$invoice['checkout_plugin_id'],'');
}
}
public function approveInvoice($id) { return $this->pApprove($id); }
/**
* Approve an invoice, which will enable services to be provisioned.
*
* @param int $id Invoice ID to approve
* @uses service
*/
public function pApprove($id) {
$db = &DB();
# Get the invoice details
$invoices = $this->sql_GetRecords(array('where'=>array('id'=>$id,'process_status'=>0)));
if (! count($invoices))
return false;
$invoice = array_pop($invoices);
# Update the invoice approval status:
$rs = $db->Execute(sqlUpdate('invoice',array('date_last'=>time(),'process_status'=>1),array('where'=>array('id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Send approval notice to user:
include_once(PATH_MODULES.'email_template/email_template.inc.php');
$mail = new email_template;
$mail->send('invoice_approved_user',$invoice['account_id'],$id,'','');
# Include the service class
include_once(PATH_MODULES.'service/service.inc.php');
$so = new service;
# Determine if services have already been created for this invoice
switch ($invoice['type']) {
# Recurring invoice, just update assoc services
case 1:
# Loop through invoice items & approve assoc services
$rs = $db->Execute(sqlSelect('invoice_item','service_id',array('where'=>array('invoice_id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Update services status
if ($rs->RecordCount())
while (! $rs->EOF) {
$so->approveService($rs->fields['service_id']);
$rs->MoveNext();
}
break;
default:
$rs = $db->Execute(sqlSelect('service','id',array('where'=>array('invoice_id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# If there are already existing services, just update their status
if ($rs->RecordCount()) {
while (! $rs->EOF) {
$so->approveService($rs->fields['id']);
$rs->MoveNext();
}
# No services exist, they can be provisioned
} else {
# Get the invoice items in this invoice
$ii = $db->Execute(sqlSelect('invoice_item','*',array('where'=>sprintf('(parent_id IN (0,"") OR parent_id IS NULL)'))));
if (! $ii) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
while (! $ii->EOF) {
if (! $ii->fields['service_id']) {
# Add the service
$so->invoiceItemToService($ii->fields['id'],$invoice);
# Check for any children items in this invoice
$iii = $db->Execute(sqlSelect('invoice_item','*',array('where'=>array('parent_id'=>$ii->fields['id'],'invoice_id'=>$id))));
if (! $iii) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
while (! $iii->EOF) {
# Add the service
$so->invoiceItemToService($iii->fields['id'],$invoice);
$iii->MoveNext();
}
} else {
# This is a domain renewal
if ($ii->fields['item_type'] == 2 && $ii->fields['domain_type'] == 'renew')
$so->renewDomain($ii,$invoice->fields['account_billing_id']);
# This is an upgrade for an existing service
else
$so->modifyService($ii,$invoice->fields['account_billing_id']);
}
$ii->MoveNext();
}
}
}
# Create a memo
$rs = $db->Execute(sqlInsert($db,'invoice_memo',array(
'date_orig'=>time(),
'invoice_id'=>$id,
'account_id'=>(defined('SESS_ACCOUNT')) ? SESS_ACCOUNT : 0,
'type'=>'approval',
'memo'=>_('Invoice Approved')
)));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
return true;
}
public function voidInvoice($id) { return $this->pVoid($id); }
/**
* Void an invoice, which will suspend services.
*/
public function pVoid($id) {
$db = &DB();
# Get the invoice details
$invoices = $this->sql_GetRecords(array('where'=>array('id'=>$id,'process_status'=>1)));
if (! count($invoices))
return false;
# Update the invoice approval status:
$rs = $db->Execute(sqlUpdate('invoice',array('date_last'=>time(),'process_status'=>0),array('where'=>array('id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Determine if services have already been created for this invoice and deactivate
$rs = $db->Execute(sqlSelect('service','id',array('where'=>array('invoice_id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Include the service class
include_once(PATH_MODULES.'service/service.inc.php');
$so = new service;
if ($rs->RecordCount()) {
# Update services to inactive status:
while (! $rs->EOF) {
$so->voidService($rs->fields['id']);
$rs->MoveNext();
}
}
# Loop through invoice items & delete assoc services
$rs = $db->Execute(sqlSelect('invoice_item','service_id',array('where'=>array('invoice_id'=>$id))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Update services to inactive status
if ($rs->RecordCount()) {
while (! $rs->EOF) {
$so->voidService($rs->fields['service_id']);
$rs->MoveNext();
}
}
# If voided, create a memo
$rs = $db->Execute(sqlInsert($db,'invoice_memo',array(
'date_orig'=>time(),
'invoice_id'=>$id,
'account_id'=>(defined('SESS_ACCOUNT')) ? SESS_ACCOUNT : 0,
'type'=>'void',
'memo'=>_('Invoice Voided')
)));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
return true;
}
/**
* Reconcile Invoice
*/
public function reconcile($VAR) {
global $C_translate,$C_debug,$C_list;
$db = &DB();
# Reconcile is disabled when payment is installed
# @todo Think of a better way (dynamic) to handle this
if (! $C_list->is_installed('payment')) {
$C_debug->alert('Reconcile is disabled, please use the payment option!');
return false;
}
# Validate amt
if ($VAR['amount'] <= 0) {
$C_debug->alert(_('Payment amount to low!'));
return false;
}
# Get the invoice details
$invoices = $this->sql_GetRecords(array('where'=>array('id'=>$VAR['id'])));
if (! count($invoices))
return false;
$invoice = array_pop($invoices);
$billing_status = 1;
if ($VAR['amount'] > $invoice['total_amt']-$invoice['billed_amt']) {
$update = $invoice['total_amt'];
$C_translate->value['invoice']['amt'] = number_format($VAR['amount']-$invoice['total_amt']-$invoice['billed_amt'],2);
$alert = $C_translate->translate('rec_over','invoice','');
} elseif ($VAR['amount'] == $invoice['total_amt']-$invoice['billed_amt']) {
$update = $invoice['total_amt'];
} else {
$update = $VAR['amount'] + $invoice['billed_amt'];
$billing_status = 0;
}
# Update the invoice record
$rs = $db->Execute(
sqlUpdate('invoice',array('date_last'=>time(),'billed_amt'=>$update,'billing_status'=>$billing_status),
array('where'=>array('id'=>$VAR['id']))));
# Create a memo
$rs = $db->Execute(sqlInsert($db,'invoice_memo',array(
'date_orig'=>time(),
'invoice_id'=>$VAR['id'],
'account_id'=>(defined('SESS_ACCOUNT')) ? SESS_ACCOUNT : 0,
'type'=>'reconcile',
'memo'=>sprintf('%s: %s (%s)',_('Payment Added to Invoice'),number_format($VAR['amount'],2),isset($VAR['memo']) ? $VAR['memo'] : '')
)));
# Receipt printing
# @todo Move this to be consistent with invoice printing.
include_once PATH_MODULES.'invoice/receipt_print.php';
$receipt = new receipt_print;
$receipt->add($invoice,number_format($VAR['amount'],2),number_format($update,2));
# Auto update if billed complete
if ($billing_status) {
$this->autoApproveInvoice($VAR['id']);
# User invoice creation confirmation
include_once(PATH_MODULES.'email_template/email_template.inc.php');
$email = new email_template;
$email->send('invoice_paid_user',$invoice['account_id'],$VAR['id'],$invoice['billed_currency_id'],'');
# Admin alert of payment processed
$email = new email_template;
$email->send('admin->invoice_paid_admin',$invoice['account_id'],$VAR['id'],$invoice['billed_currency_id'],'');
}
# Redirect
if (! empty($VAR['redirect'])) {
printf('<script type="text/javascript">window.parent.location="%s";',$VAR['redirect']);
if (! empty($alert))
printf('alert("%s");',$alert);
echo '</script>';
exit;
}
$C_debug->alert($C_translate->translate('ref_comp','invoice',''));
return;
}
/**
* Refund Invoice
*/
public function refund($VAR) {
global $C_translate,$C_debug;
# Validate amt
if ($VAR['amount'] <= 0) {
$C_debug->alert(_('Refund amount to low!'));
return false;
}
$update = $this->getRecordAttr('billed_amt')-$VAR['amount'];
$billing_status = ($update>0) ? 1 : 0;
# Update the invoice record
$rs = $db->Execute(
sqlUpdate('invoice',
array('date_last'=>time(),'billed_amt'=>$update,'billing_status'=>$billing_status,'suspend_billing'=>1,'refund_status'=>1),
array('where'=>array('id'=>$VAR['id']))));
if (! $rs) {
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Create a memo
$rs = $db->Execute(sqlInsert($db,'invoice_memo',array(
'date_orig'=>time(),
'invoice_id'=>$VAR['id'],
'account_id'=>(defined('SESS_ACCOUNT')) ? SESS_ACCOUNT : 0,
'type'=>'refund',
'memo'=>sprintf('%s: %s (%s)',_('Refunded Invoice'),number_format($VAR['amount'],2),isset($VAR['memo']) ? $VAR['memo'] : '')
)));
if (! $rs) {
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Void
$this->pVoid($VAR['id']);
# Call into the checkout plugin and attempt realtime refund
$C_debug->alert('Realtime refund processing not enabled.');
if (! true) {
$billing = $db->Execute(
sqlSelect($db,
array('account_billing','checkout'),
'A.*,B.checkout_plugin',sprintf('A.id=%s AND A.checkout_plugin_id=B.id',$this->getRecordAttr('account_billing_id'))));
if ($billing && $billing->RecordCount() && ! empty($billing->fields['checkout_plugin'])) {
$plugin_file = sprintf('%scheckout/%s.php',PATH_PLUGINS,$billing->fields['checkout_plugin']);
if (is_file($plugin_file)) {
include_once($plugin_file);
eval(sprintf('$PLG = new plg_chout_%s("%s");',$billing->fields['checkout_plugin'],$billing->fields['checkout_plugin_id']));
if (is_callable(array($PLG,'refund')))
$PLG->refund($this->getRecord(),$billing->fields,$VAR['amount']);
}
}
}
# Redirect
if (! empty($VAR['redirect'])) {
printf('<script type="text/javascript">window.parent.location="%s";',$VAR['redirect']);
return;
}
$C_debug->alert($C_translate->translate('ref_comp','invoice',''));
return;
}
/**
* Resend due notice
*
* @uses email_template
*/
public function resend($VAR) {
global $C_debug;
$db = &DB();
# User invoice creation confirmation
include_once(PATH_MODULES.'email_template/email_template.inc.php');
$mail = new email_template;
$mail->send('invoice_resend',$this->getRecordAttr('account_id'),$this->getRecordAttr('id'),'','');
# Alert
$C_debug->alert('Sent payment due notice to user');
# Update invoice
$db->Execute(sqlUpdate('invoice',array('notice_count'=>$this->getRecordAttr('notice_count')+1),array('id'=>$this->getRecordAttr('id'))));
}
/**
* Initialise an invoice
*
* This function is responsible for getting all the information required to render an invoice.
*/
private function initInvoicePrint() {
# Check admin authentication:
global $C_auth;
$db = &DB();
if ($C_auth->auth_method_by_name('invoice','pdf') == false) {
if ($this->getRecordAttr('account_id') != SESS_ACCOUNT)
return false;
}
$invoice = array();
#@todo this should be in setup_invoice
$invoice['site']['TAXID'] = SITE_TAXID;
$invoice['site']['NAME'] = SITE_NAME;
$invoice['site']['ADDRESS'] = SITE_ADDRESS;
$invoice['site']['CITY'] = SITE_CITY;
$invoice['site']['STATE'] = SITE_STATE;
$invoice['site']['ZIP'] = SITE_ZIP;
$invoice['site']['FAX'] = SITE_FAX;
$invoice['site']['PHONE'] = SITE_PHONE;
$invoice['site']['EMAIL'] = SITE_EMAIL;
$invoice['site']['URL'] = URL;
# Invoice Configuration
$rs = $db->Execute(sqlSelect($db,'setup_invoice','*',''));
$invoice['invcfg'] = $rs->fields;
# Invoice details
$rs = $db->Execute(sqlSelect($db,array('invoice','currency'),'A.*,B.symbol',sprintf('A.id=%s AND B.id=A.billed_currency_id',$this->getRecordAttr('id'))));
$invoice['invoice'] = $rs->fields;
# Account detail:
$rs = $db->Execute(sqlSelect($db,'account','*',array('id'=>$this->getRecordAttr('account_id'))));
$invoice['account'] = $rs->fields;
# If we get here, all is OK.
return $invoice;
}
/**
* Display a HTML invoice in the browser or email
*/
public function html($VAR) {
global $C_list;
if (! isset($this->record) && isset($VAR['id']))
$this->sql_LoadRecord($VAR['id']);
# Get our invoice details
if (! $this->print = $this->initInvoicePrint())
return false;
$return = '';
$return .= '<style type="text/css">';
$return .= 'table.data {width: 100%;vertical-align: top;empty-cells: show;border: 1px solid #AAAACC;border-spacing: 0px;background-color: #FEFEFE;}';
$return .= '</style>';
$return .= '<table border="0" style="width: 100%;">';
$return .= '<tr>';
#$return .= sprintf('<td style="width: 15%%;"><img src="cid:logoimg" alt="logo"/></td>',sprintf('%s/%s','/ab/themes/'.DEFAULT_THEME,'invoice/invoice-logo.png'));
$return .= '<td style="width: 5%;"><img src="cid:logoimg" alt="logo"/></td>';
$return .= '<td colspan="3" style="vertical-align: top;">';
$return .= '<table border="0" width="100%" style="blank">';
$return .= sprintf('<tr><td>%s</td></tr>',$this->print['site']['NAME']);
$return .= sprintf('<tr><td style="font-size: 80%%;">%s %s %s</td></tr>',
$this->print['site']['ADDRESS'],$this->print['site']['STATE'],$this->print['site']['ZIP']);
$return .= '<tr><td>&nbsp;</td></tr>';
$return .= sprintf('<tr><td style="font-size: 80%%;">%s</td></tr>',$this->print['site']['TAXID']);
$return .= '</table>';
$return .= '</td>';
$return .= '</tr>';
$return .= '<tr><td>&nbsp;</td></tr>';
$return .= sprintf('<tr><td colspan="2" style="width: 50%%";>&nbsp</td><td>%s</td><td style="text-align: right;"><b>%s</b></td></tr>',
_('Tax Invoice'),$this->getPrintInvoiceNum());
$return .= sprintf('<tr><td colspan="2">&nbsp</td><td>%s</td><td style="text-align: right;"><b>%s</b></td></tr>',
_('Issue Date'),date(UNIX_DATE_FORMAT,$this->print['invoice']['date_orig']));
$return .= sprintf('<tr><td colspan="2">&nbsp</td><td>%s</td><td style="text-align: right;"><b>%s</b></td></tr>',
_('Amount Due'),date(UNIX_DATE_FORMAT,$this->print['invoice']['due_date']));
$return .= sprintf('<tr><td colspan="2">&nbsp</td><td>%s</td><td style="text-align: right;"><b>%s</b></td></tr>',
_('Current Charges'),$C_list->format_currency_num($this->print['invoice']['total_amt'],$this->getRecordAttr('currency_id')));
$return .= '<tr><td>&nbsp;</td></tr>';
$return .= '<td colspan="4" style="vertical-align: top;">';
$return .= '<table border="0" width="100%" class="data">';
foreach ($this->sInvoiceItems() as $index => $items) {
# Get the date range if set
if (! empty($items['date_start']) && ! empty($items['date_stop'])) {
global $C_translate;
$C_translate->value('invoice','start',date(UNIX_DATE_FORMAT,$items['date_start']));
$C_translate->value('invoice','stop',date(UNIX_DATE_FORMAT,$items['date_stop']));
}
$return .= sprintf('<tr><td>%s</td><td>%s</td><td style="text-align: right;">%s</td></tr>',
$items['quantity'],$this->sLineItemDesc($index),$C_list->format_currency_num($items['price_base'],$this->getRecordAttr('currency_id')));
if ($items['price_setup'])
$return .= sprintf('<tr><td>&nbsp;</td><td>%s %s</td><td style="text-align: right;">%s</td></tr>',
$this->sLineItemDesc($index),_('Setup'),$C_list->format_currency_num($items['price_base'],$this->getRecordAttr('currency_id')));
}
$return .= '<tr><td>&nbsp;</td></tr>';
if ($this->print['invoice']['discount_amt'])
$return .= sprintf('<tr style="font-size: 80%%;"><td>&nbsp;</td><td>%s</td><td style="text-align: right;">%s</td></tr>',
_('Discount'),$C_list->format_currency_num($this->print['invoice']['discount_amt'],$this->getRecordAttr('currency_id')));
if ($this->print['invoice']['tax_amt'])
$return .= sprintf('<tr style="font-size: 80%%;"><td>&nbsp;</td><td>%s</td><td style="text-align: right;">%s</td></tr>',
_('Taxes'),$C_list->format_currency_num($this->print['invoice']['tax_amt'],$this->getRecordAttr('currency_id')));
$return .= '<tr><td>&nbsp;</td></tr>';
$return .= sprintf('<tr><td>&nbsp;</td><td><b>%s</b></td><td style="text-align: right;"><b>%s</b></td></tr>',
_('Total Due'),$C_list->format_currency_num($this->print['invoice']['total_amt'],$this->getRecordAttr('currency_id')));
$return .= '</table>';
$return .= '</td>';
$return .= '</tr>';
$return .= '</table>';
return $return;
}
/**
* Display a PDF invoice in the browser for download.
*
* This method can be called directly via do[].
*
* @return string PDF Output (only if dest=>S), otherwise PDF returns an empty string.
*/
public function pdf($VAR,$object,$args=array('dest'=>'I')) {
if (! isset($this->record) && isset($VAR['id']))
$this->sql_LoadRecord($VAR['id']);
if (! $pdf = $this->initInvoicePDF())
return false;
# If we called as a do[] method, we need to set the output correctly
if (isset($VAR['_page']))
$args = array('dest'=>'I');
$this->pdfInvoiceSummary($pdf);
return $pdf->Output($args['file'] ? $args['file'] : sprintf('%s.pdf',$this->getPrintInvoiceID()),$args['dest']);
}
/**
* Initialise a PDF invoice
*
* This function is resonsible for setting up a PDF invoice
*/
private function initInvoicePDF() {
# Get our invoice details
if (! $this->print = $this->initInvoicePrint())
return false;
#@todo since the view template dynamic finds available plugins, this should also find the plugin (incase the prefix/dir is moved).
require_once(sprintf('%sinvoice/PDF/pdf_invoice_%s.inc.php',PATH_MODULES,$this->print['invcfg']['invoice_pdf_plugin']));
$pdf = new pdf_invoice_overview($this);
$pdf->load_setup();
if ($pdf->getTemplate()) {
$pagecount = $pdf->setSourceFile($pdf->getTemplate());
$tplidx = $pdf->ImportPage(1);
}
$pdf->addPage();
# If we are using FPDI
if (isset($tplidx))
$pdf->useTemplate($tplidx);
# If we get here, all is OK.
return $pdf;
}
/** HERE **/
/** Export multiple invoices */
function pdfExport(&$rs)
{
$db =& DB();
ob_start();
$pdf = new pdf_invoice_overview();
$pdf->companyName = SITE_NAME;
$pdf->companyAddress = SITE_ADDRESS;
$pdf->companyCity = SITE_CITY;
$pdf->companyState = SITE_STATE;
$pdf->companyZip = SITE_ZIP;
$pdf->load_setup();
if ($pdf->getTemplate())
$pagecount = $pdf->setSourceFile($pdf->getTemplate());
$tplidx = $pdf->ImportPage(1);
while(!$rs->EOF) {
$pdf->addPage();
$pdf->useTemplate($tplidx);
$this->pdfInvoiceSummary($pdf);
$rs->MoveNext();
unset($pdf->itemsSummary);
}
$pdf->Output();
ob_end_flush();
}
/**
* Render an invoice with the summary page
*
* @todo Draw discounts
* @todo Draw tax details
*/
private function pdfInvoiceSummary($pdf) {
# Invoice details:
$db = &DB();
# Draw Invoice Basics
$pdf->drawCompanyLogo();
$pdf->drawCompanyAddress($this);
$pdf->drawInvoiceHeader($this);
$pdf->drawNews($this->print['invcfg']['news']);
$pdf->drawRemittenceStub($this);
$pdf->drawPaymentMethods($this);
if ($this->print['invoice']['billing_status'] !=1 && $this->print['invoice']['suspend_billing'] != 1 && $this->print['invoice']['due_date'] <= time())
$pdf->drawInvoiceDueNotice();
elseif($this->print['invoice']['billing_status'] == 1)
$pdf->drawInvoicePaidNotice();
if ($this->sPreviousBalance())
$pdf->drawSummaryInvoicesDue($this->sPreviousInvoices());
$pdf->drawSummaryLineItems($this);
unset($pdf->itemsSummary);
# BEGIN loop for enumerating information in multiple ways on the invoice
$iteration = 0;
while ($pdf->drawLineItems_pre($iteration)) {
foreach ($this->sInvoiceItems() as $index => $items) {
# Get the date range if set
if (! empty($items['date_start']) && ! empty($items['date_stop'])) {
global $C_translate;
$C_translate->value('invoice','start',date(UNIX_DATE_FORMAT,$items['date_start']));
$C_translate->value('invoice','stop',date(UNIX_DATE_FORMAT,$items['date_stop']));
}
$line = array(
'name'=>$this->sLineItemDesc($index),
'domain'=>$items['domain_name'],
'amount'=>$items['price_base'],
'sku'=>$items['sku'],
'qty'=>$items['quantity'],
'cost'=>$items['price_base'],
'attr'=>$items['product_attr'],
'price_type'=>$items['price_type'],
'price_base'=>$items['price_base'],
'item_type'=>$items['item_type'],
'total_amt'=>$items['total_amt']
);
if ($items['date_start'] && $items['date_stop'])
if ($items['date_start'] == $items['date_stop'])
$line['daterange'] = sprintf('%s',date(UNIX_DATE_FORMAT,$items['date_start']));
else
$line['daterange'] = sprintf('%s - %s',date(UNIX_DATE_FORMAT,$items['date_start']),date(UNIX_DATE_FORMAT,$items['date_stop']));
$pdf->drawLineItems($db,$line,$this->getRecordAttr('id'));
if ($items['price_setup']) {
$line = array(
'name'=>sprintf('%s - %s',$this->sLineItemDesc($index),_('Setup Charge')),
'amount'=>$items['price_setup'],
'qty'=>'1',
'sku'=>$items['sku'],
'cost'=>$items['price_setup'],
'price_base'=>$items['price_setup'],
'price_type'=>999
);
$pdf->drawLineItems($db,$line,$this->getRecordAttr('id'));
}
}
if ($this->print['invoice']['discount_amt']) {
$line = array(
'name'=>_('Discount'),
'amount'=>-($this->print['invoice']['discount_amt']),
'qty'=>'1',
'cost'=>-($this->print['invoice']['discount_amt']),
'price_base'=>-($this->print['invoice']['discount_amt']),
'price_type'=>999);
$pdf->drawLineItems($db,$line,$this->getRecordAttr('id'));
}
if ($this->print['invoice']['tax_amt']) {
$rs = $db->Execute(sqlSelect($db,array('invoice_item_tax','tax'),'A.amount,B.description',sprintf('A.tax_id=B.id AND A.invoice_id=%s',$this->getRecordAttr('id'))));
if ($rs && $rs->RecordCount()) {
$taxes = array();
while (! $rs->EOF) {
if (! isset($taxes[$rs->fields['description']]))
$taxes[$rs->fields['description']] = $rs->fields['amount'];
else
$taxes[$rs->fields['description']] += $rs->fields['amount'];
$rs->MoveNext();
}
foreach ($taxes as $txds => $txamt) {
$line = array('name'=>$txds,'amount'=>$txamt,'total_amt'=>$txamt,'price_type'=>999);
$pdf->drawLineItems($db,$line,$this->getRecordAttr('id'));
}
}
}
# Increment the iteration
++$iteration;
}
# Custom functions:
$pdf->drawCustom();
}
/**
* Generate all invoices for recurring services/charges/domains
*/
public function task_GenerateRecurrInvoices() {
global $C_list;
$db = &DB();
$rs = $db->Execute($this->sql_InvoiceSoon());
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
# There are invoices.
} elseif ($rs->RecordCount()) {
$ids = '';
$account = '';
$date = '';
$invoice = '';
# Group all the service IDs and generate one invoice for all services.
while (! $rs->EOF) {
if ($ids && (($rs->fields['account_id'] != $account) || ($rs->fields['invoice_date'] != $date))) {
$io = new invoice;
$io->generateInvoice($ids,$invoice);
$ids = '';
}
# Set the current account and date
$account = $rs->fields['account_id'];
$invoice = $rs->fields['iid'];
$date = $rs->fields['invoice_date'];
# Add to id list
if ($ids)
$ids .= ','.$rs->fields['sid'];
else
$ids = $rs->fields['sid'];
$rs->MoveNext();
}
if ($ids) {
$io = new invoice;
$io->generateInvoice($ids,$invoice);
}
}
# Generate invoices for any domains expiring in X days.
if ($C_list->is_installed('host_tld'))
$this->generateDomains();
return true;
}
public function generateinvoice_account($VAR) {
# Check if charge module installed
global $C_list;
$db = &DB();
$rs = $db->Execute($this->sql_InvoiceSoon(null,0,$VAR['account_id']));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return;
}
# Set the invoice and date
$invoice = $rs->fields['iid'];
$date = $rs->fields['invoice_date'];
$ids = '';
while (! $rs->EOF) {
if ($ids && ($rs->fields['invoice_date'] != $date)) {
$io = new invoice;
$io->generateInvoice($ids,$invoice);
$ids = '';
}
# Add to id list
if ($ids)
$ids .= ',';
$ids .= $rs->fields['sid'];
$rs->MoveNext();
}
$io = new invoice;
if ($ids)
$io->generateInvoice($ids,$invoice);
if (isset($VAR['_page_next']))
define('REDIRECT_PAGE','?_page='.$VAR['_page_next']);
}
/**
* Generate an Invoice for the service Ids
*/
private function generateInvoice($sids,$piid) {
global $C_list;
# If there are no service ID's, we'll just return
if (empty($sids))
return false;
# Load required elements
include_once(PATH_MODULES.'account/account.inc.php');
include_once(PATH_MODULES.'service/service.inc.php');
include_once(PATH_MODULES.'product/product.inc.php');
$afo = false;
if ($C_list->is_installed('account_fee')) {
include_once(PATH_MODULES.'account_fee/account_fee.inc.php');
$afo = new account_fee;
}
# Start a transaction
$db = &DB();
if (AGILE_DB_TYPE == 'mysqlt') {
$db->StartTrans();
if (! $db->hasTransactions) {
global $C_debug;
$msg = "Transactions not supported in 'mysql' driver. Use 'mysqlt' or 'mysqli' driver";
$C_debug->alert($msg);
$C_debug->error(__FILE__,__METHOD__,$msg);
return false;
}
}
# Start
$this->clearRecord();
# Generate an invoice id
$this->setRecordAttr('id',sqlGenID($db,'invoice'));
# Beginning totals
$invoice = array();
$invoice['recur_schedules'] = array();
foreach (explode(',',$sids) as $sid) {
$so = new service($sid);
$po = new product($so->getRecordAttr('product_id'));
if (! isset($ao)) {
$ao = new account($so->getRecordAttr('account_id'));
$this->setRecordAttr('account_id',$so->getRecordAttr('account_id'));
$this->setRecordAttr('account_billing_id',$so->getRecordAttr('account_billing_id'));
$this->setRecordAttr('billed_currency_id',$ao->getRecordAttr('currency_id'));
$this->setRecordAttr('actual_billed_currency_id',DEFAULT_CURRENCY);
$this->setRecordAttr('reseller_id',$ao->getRecordAttr('reseller_id'));
$this->setRecordAttr('checkout_plugin_id',$ao->getRecordAttr('checkout_plugin_id'));
$this->setRecordAttr('checkout_plugin_data',$ao->getRecordAttr('checkout_plugin_data'));
$this->setRecordAttr('grace_period',$ao->getRecordAttr('invoice_grace'));
# @todo this may unintentially allocate all service revenue to an affiliate, which should be configurable (not just the service that the account signed up for initially)
$this->setRecordAttr('affiliate_id',$ao->getRecordAttr('affiliate_id'));
# @todo the parent invoice should bring this campaign id.
$this->setRecordAttr('campaign_id',null);
}
$this->setRecordAttr('due_date',$so->getRecordAttr('date_next_invoice'));
$last_invoice = $so->getRecordAttr('date_next_invoice');
$next_invoice = $so->getRecordAttr('date_next_invoice');
while ($next_invoice < (time()+($this->sInvoiceDays()*86400))) {
if ($po->getRecord()) {
$billdates = $po->recurrDates($so->getRecordAttr('recur_schedule'),$so->getRecordAttr('recur_weekday'),null,$next_invoice);
$next_invoice = $billdates['end'];
$x = $billdates['end'];
} else {
$x = $next_invoice;
$next_invoice = $so->calcNextInvoiceDate(
$last_invoice,$so->getRecordAttr('recur_schedule'),$so->getRecordAttr('recur_type'),$so->getRecordAttr('recur_weekday'));
}
$iid = $this->aaddItem(array(
'charge_id'=>null,
'date_start'=>$last_invoice,
'date_stop'=>$next_invoice,
'domain_name'=>$so->getRecordAttr('domain_name'),
'domain_tld'=>$so->getRecordAttr('domain_tld'),
'domain_type'=>$so->getRecordAttr('domain_type'),
'domain_term'=>$so->getRecordAttr('domain_term'),
'item_type'=>0,
'price_setup'=>0,
'price_base'=>$so->getRecordAttr('price'),
'price_type'=>$so->getRecordAttr('price_type'),
'product_id'=>$so->getRecordAttr('product_id'),
'product_attr'=>$so->getRecordAttr('prod_attr'),
'product_attr_cart'=>null,
'quantity'=>1,
'recurring_schedule'=>$so->getRecordAttr('recur_schedule'),
'service_id'=>$so->getRecordAttr('id'),
'type'=>$so->getRecordAttr('type')
));
$last_invoice = $x;
}
array_push($invoice['recur_schedules'],$so->getRecordAttr('recur_schedule'));
# Update the last & next invoice date for this service
$rs = $db->Execute(sqlUpdate($db,'service',
array('date_last_invoice'=>$so->getRecordAttr('date_next_invoice'),'date_next_invoice'=>$next_invoice),
array('id'=>$so->getRecordAttr('id'))));
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
$db->FailTrans();
return false;
}
# Get any charges for this service and create them as invoice items
if ($C_list->is_installed('charge')) {
include_once(PATH_MODULES.'charge/charge.inc.php');
$co = new charge();
foreach ($co->sql_GetRecords(
array('where'=>sprintf('(status=0 OR status IS NULL) AND service_id=%s',
$so->getRecordAttr('id'),$so->getRecordAttr('date_next_invoice')))) as $record) {
$iid = $this->aaddItem(array(
'charge_id'=>$record['id'],
'date_start'=>$record['date_orig'],
'date_stop'=>$record['date_orig'],
'domain_name'=>null,
'domain_tld'=>null,
'domain_type'=>null,
'domain_term'=>null,
'item_type'=>5,
'price_setup'=>0,
'price_base'=>$record['amount'],
'price_type'=>3,
'product_id'=>$record['product_id'],
'product_name'=>$record['description'],
'product_attr'=>$record['attributes'],
'product_attr_cart'=>null,
'quantity'=>$record['quantity'],
'recurring_schedule'=>null,
'service_id'=>$so->getRecordAttr('id'),
'sku'=>$record['sku']
));
# Update charge status
$rs = $db->Execute(sqlUpdate($db,'charge',array('status'=>1),array('id'=>$record['id'])));
if (false && ! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
$db->FailTrans();
return false;
}
}
}
}
# See if there is an account fee for this invoice
if ($afo && count($invoice['recur_schedules'])) {
foreach ($invoice['recur_schedules'] as $recur_schedule) {
include_once(PATH_MODULES.'invoice_item/invoice_item.inc.php');
$ito = new invoice_item();
if ($fee = $afo->sAccountFee($ao->getRecordAttr('id'),$recur_schedule)) {
# Create the invoice item
$ito->setRecordAttr('charge_id',null);
$ito->setRecordAttr('invoice_id',$this->getRecordAttr('id'));
$ito->setRecordAttr('account_id',$ao->getRecordAttr('id'));
$ito->setRecordAttr('service_id',null);
$ito->setRecordAttr('product_id',null);
$ito->setRecordAttr('product_attr',null);
$ito->setRecordAttr('product_name',_('Account Fee'));
$ito->setRecordAttr('sku','ACCOUNT_FEE');
$ito->setRecordAttr('quantity',1);
$ito->setRecordAttr('item_type',6);
$ito->setRecordAttr('price_type',0);
$ito->setRecordAttr('price_base',$fee);
$ito->setRecordAttr('price_setup',0);
$ito->setRecordAttr('discount_amt',0);
$ito->setRecordAttr('recurring_schedule',$recur_schedule);
}
}
}
# Add any taxes
# Get invoice grace period from global/account
if (! empty($this->invoice_grace))
$grace_period = $this->invoice_grace;
else
$grace_period = GRACE_PERIOD;
$this->setRecordAttr('notice_next_date',time());
$this->setRecordAttr('billing_status',0);
$this->setRecordAttr('print_status',0);
$this->setRecordAttr('process_status',0);
$this->setRecordAttr('status',1);
// $this->setRecordAttr('suspend_billing',0);
$this->setRecordAttr('billed_amt',0);
$this->setRecordAttr('actual_billed_amt',0);
$this->setRecordAttr('notice_count',0);
$this->setRecordAttr('type',1);
$this->setRecordAttr('notice_max',MAX_BILLING_NOTICE);
$rs = $this->sql_SaveRecord(true);
if (! $rs) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
$db->FailTrans();
return false;
}
if (AGILE_DB_TYPE == 'mysqlt')
$db->CompleteTrans();
}
/** Invoice expiring domains
*/
function generateDomains()
{
$db = &DB();
define('DEFAULT_DOMAIN_INVOICE', 30); //how far out to generate expiring domain invoices
$expire = time() + (DEFAULT_DOMAIN_INVOICE*86400);
### Get domains expiring soon:
$rs = $db->Execute( sqlSelect( $db, 'service', '*', " active=1 AND domain_date_expire <= $expire AND type = 'domain' AND queue = 'none' AND
( domain_type = 'register' OR domain_type = 'transfer' OR domain_type = 'renew' ) AND
( suspend_billing = 0 OR suspend_billing IS NULL) " ) );
if($rs && $rs->RecordCount() > 0 ) {
while(!$rs->EOF) {
# Check that this domain has not already been invoiced
$invoiced = $db->Execute(sqlSelect ($db, array('invoice_item','invoice'), array('A.*','B.*'),
" A.invoice_id = B.id AND A.service_id = {$rs->fields['id']} AND A.sku = 'DOMAIN-RENEW' AND domain_type = 'renew' AND
date_start = {$rs->fields['date_last_invoice']} AND date_stop = {$rs->fields['domain_date_expire']}" ) );
if($invoiced && $invoiced->RecordCount() == 0) {
# Not previously invoiced, generate now!
$this->generatedomaininvoice( $rs->fields, $this );
}
$rs->MoveNext();
}
}
}
/** Invoice expiring domains p2
*/
function generatedomaininvoice($VAR)
{
include_once(PATH_MODULES . 'tax/tax.inc.php');
$taxObj = new tax;
$db = &DB();
if( is_array( $VAR ) ) {
$expire = time();
$rs = $db->Execute(sqlSelect($db, 'service', '*', " id = ::{$VAR['id']}:: AND active=1
AND type = 'domain' AND queue = 'none' AND
(domain_type = 'register' OR domain_type = 'transfer' OR domain_type = 'renew') AND
(suspend_billing = 0 OR suspend_billing IS NULL) "));
$service = $rs->fields;
} else {
$service = $VAR;
}
if(empty($service['id'])) {
global $C_debug;
$C_debug->alert("Unable to generate domain renweal invoice due to domain status.");
return false;
}
# Get the parent invoice details:
if(!empty($service['invoice_id'])) {
$rs = $db->Execute(sqlSelect($db, 'invoice', '*', " id = {$service['invoice_id']} ", ""));
$invoice = $rs->fields;
} else {
$invoice = false;
}
# Get the account details:
$rs = $db->Execute(sqlSelect($db, 'account', '*', " id = {$service['account_id']} ", ""));
$account = $rs->fields;
# Get the account price
include_once(PATH_MODULES.'host_tld/host_tld.inc.php');
$tldObj=new host_tld;
$tld_arr = $tldObj->price_tld_arr($service['domain_tld'], 'renew', false, false, false, $service['account_id']);
foreach($tld_arr as $term => $price) break;
# Calculate taxes:
$rs = $db->Execute($sql=sqlSelect($db,"host_tld","taxable","name = ::{$service['domain_tld']}::"));
if( $service['taxable'] || @$rs->fields['taxable'] ) {
$tax_arr = $taxObj->calculate($price, $account["country_id"], $account["state"]);
} else {
$tax_arr = false;
}
$total = $price;
$tax_amt = 0;
if(is_array($tax_arr)) {
foreach($tax_arr as $tx) {
$tax_amt += $tx['rate'];
}
$total += $tax_amt;
}
# calculate the dates
$expire = $service['domain_date_expire'] + ($term*86400);
$due_date = $service['domain_date_expire'] - (86400*3);
# Create the invoice
$id = sqlGenID($db, "invoice");
$insert = $db->Execute($sql = sqlInsert($db, "invoice",
array(
'date_orig' => time(),
'date_last' => time(),
'type' => 2,
'process_status' => 0,
'billing_status' => 0,
'suspend_billing' => 0,
'print_status' => 0,
'parent_id' => $service['invoice_id'],
'account_id' => $service['account_id'],
'account_billing_id'=> $service['account_billing_id'],
'affiliate_id' => @$invoice['affiliate_id'],
'campaign_id' => @$invoice['campaign_id'],
'reseller_id' => @$invoice['reseller_id'],
'checkout_plugin_id'=> @$invoice['checkout_plugin_id'],
'tax_amt' => $tax_amt,
'discount_arr' => serialize(@$discount_arr),
'discount_amt' => @$discount_amt,
'total_amt' => $total,
'billed_amt' => 0,
'billed_currency_id'=> DEFAULT_CURRENCY,
'actual_billed_amt' => 0,
'actual_billed_currency_id' => @$invoice['actual_billed_currency_id'],
'notice_count' => 0,
'notice_next_date' => time(),
'notice_max' => MAX_BILLING_NOTICE,
'grace_period' => 0,
'due_date' => $due_date
), $id)) ;
# create the invoice item:
if($insert) {
$db->Execute ($idx = sqlInsert($db, "invoice_item",
array(
'date_orig' => time(),
'invoice_id' => $id,
'account_id' => $service['account_id'],
'service_id' => $service['id'],
'sku' => 'DOMAIN-RENEW',
'quantity' => 1,
'item_type' => 2,
'price_type' => 0,
'price_base' => $price,
'price_setup' => 0,
'domain_type' => 'renew',
'date_start' => $service['domain_date_expire'],
'date_stop' => $expire,
'domain_name' => $service['domain_name'],
'domain_tld' => $service['domain_tld'],
'domain_term' => $term,
'tax_amt' => $tax_amt,
'total_amt' => $price
)));
# Insert tax records
$taxObj->invoice_item($id, $idx, $service['account_id'], @$item_tax_arr);
# Update the service record
$fields=array('active' => 0);
$db->Execute(sqlUpdate($db,"service",$fields,"id = {$service['id']}"));
global $C_debug;
$C_debug->alert("Generated domain renewal invoice for {$service['domain_name']}.{$service['domain_tld']}");
return $id;
}
}
/** Run AutoBilling and Due Notices
*/
function autobill($VAR)
{
global $VAR, $C_debug, $C_list;
# User invoice creation confirmation
include_once(PATH_MODULES.'email_template/email_template.inc.php');
$mail = new email_template;
# get all due invoices
$db = &DB();
#$db->debug = true;
if(empty($VAR['invoice_id']))
{
$this->bill_one = false;
$sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'invoice
WHERE notice_next_date <= ' . $db->qstr( time() ) . '
AND (
billing_status = 0 OR
billing_status IS NULL
) AND (
suspend_billing = 0 OR
suspend_billing IS NULL
)
AND site_id = ' . $db->qstr(DEFAULT_SITE);
$invoice = $db->Execute($sql);
if($invoice->RecordCount() == 0) {
$C_debug->alert('No Invoices to Autobill');
return false;
}
} else {
# get the specified invoice:
$this->bill_one = true;
$sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'invoice
WHERE (
billing_status = 0 OR
billing_status IS NULL
)
AND id = ' . $db->qstr($VAR['invoice_id']) . '
AND site_id = ' . $db->qstr(DEFAULT_SITE);
$invoice = $db->Execute($sql);
}
# Check for results
if($invoice->RecordCount() == 0) {
$C_debug->alert('Invoice could not be billed!');
return false;
}
# Loop through results
while(!$invoice->EOF)
{
$db->StartTrans();
$due = true;
# get currency code
$cyid = $invoice->fields['actual_billed_currency_id'];
$billed_currency_id = $invoice->fields['billed_currency_id'];
if(empty($this->currency_iso[$cyid]))
{
$q = "SELECT three_digit,convert_array FROM ". AGILE_DB_PREFIX ."currency WHERE
id = ". $db->qstr($cyid)." AND
site_id = ". $db->qstr(DEFAULT_SITE);
$currb = $db->Execute($q);
$this->format_currency[$cyid] = array('convert' => unserialize($currb->fields["convert_array"]),
'iso' => $currb->fields["three_digit"]);
}
# get the currency codes (default)
if(empty($this->format_currency[$billed_currency_id]))
{
# Get the billed currency id currency info:
$q = "SELECT three_digit,convert_array FROM ".AGILE_DB_PREFIX."currency WHERE
id = ".$db->qstr($billed_currency_id)." AND
site_id = ".$db->qstr(DEFAULT_SITE);
$currb = $db->Execute($q);
$this->format_currency[$billed_currency_id] = array('convert' => unserialize($currb->fields["convert_array"]),
'iso' => $currb->fields["three_digit"]);
}
# attempt to autobill?
if(!empty($invoice->fields['account_billing_id']))
{
# get checkout plugin details:
$billing =& $db->Execute($sql=sqlSelect($db, array('account_billing','checkout'), 'A.*,B.checkout_plugin',
"A.id = ::{$invoice->fields['account_billing_id']}:: AND A.checkout_plugin_id=B.id"));
if($billing && $billing->RecordCount() == 1 && !empty($billing->fields['checkout_plugin'])) {
$plugin_file = PATH_PLUGINS.'checkout/'. $billing->fields['checkout_plugin'] .'.php';
if(!is_file($plugin_file)) {
$err = $plugin_file .' missing when autobilling invoice id ' . $invoice->fields['id'];
$C_debug->error(__FILE__,__METHOD__,$err);
} else {
include_once ($plugin_file);
eval('$PLG = new plg_chout_' . $billing->fields['checkout_plugin'] . '("'.$billing->fields['checkout_plugin_id'].'");');
}
} else {
$err = 'account_billing.id '.$invoice->fields['account_billing_id'].' empty or not associated with a checkout plugin when autobilling invoice id ' . $invoice->fields['id'];
$C_debug->error(__FILE__,__METHOD__,$err);
}
}
# get the actual billed amount
$amount = $invoice->fields['total_amt'] - $invoice->fields['billed_amt'];
$billed_amt = $invoice->fields['total_amt'];
$actual_billed_amt = $invoice->fields['total_amt'];
if($amount <= 0) $due = false;
if(!empty($PLG) && is_object($PLG) && $PLG->type == 'gateway' && $amount > 0)
{
# attempt autobilling if account billing exists and gateway plugin
if($invoice->fields['account_billing_id'] > 0)
{
/* get the account details */
$account = $db->Execute(sqlSelect($db,"account","id,email","id=::{$invoice->fields['account_id']}"));
/* Convert the invoice amount to the actual billed currency amount */
if($cyid != $invoice->fields['billed_currency_id']) {
$conversion = $this->format_currency[$billed_currency_id]["convert"][$cyid]["rate"];
$amount *= $conversion;
$actual_billed_amt = $invoice->fields['actual_billed_amt'] + $amount;
}
/* load the billing details from the database */
$PLG->setBillingFromDBObj($billing, true);
/* attempt to auto-bill */
if(!$checkout_plugin_data = $PLG->bill_checkout( number_format($amount,2), $invoice->fields['id'], $this->format_currency[$cyid]['iso'], $account->fields, 0,0) ) {
$due = true;
$email = new email_template;
$email->send('invoice_decline_user', $invoice->fields['account_id'], $invoice->fields['id'],$C_list->format_currency($invoice->fields['total_amt'],$cyid), $C_list->date($invoice->fields['due_date']));
$email = new email_template;
$email->send('admin->invoice_decline_admin', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],''), $C_list->date($invoice->fields['due_date']));
} else {
$due = false;
}
}
}
# send proper alert & manage services
if ($due)
{
# determine if overdue
$due = $invoice->fields['due_date'];
$grace = $invoice->fields['grace_period'];
if(time() < $due+(86400*$grace))
{
if($invoice->fields['notice_count'] <= 0)
{
# send out first alert - new invoice created!
$email = new email_template;
$email->send('invoice_recur_user', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],$cyid), $C_list->date($invoice->fields['due_date']));
$email = new email_template;
$email->send('admin->invoice_recur_admin', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],''), $C_list->date($invoice->fields['due_date']));
}
else
{
# send out payment due notice
if(empty($PLG) || $PLG->type == 'gateway') {
$email = new email_template;
$email->send('invoice_due_user', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$cyid]["iso"], $C_list->date($invoice->fields['due_date']));
$email = new email_template;
$email->send('admin->invoice_due_admin', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$billed_currency_id]["iso"], $C_list->date($invoice->fields['due_date']));
}
elseif($PLG->type == 'redirect') {
$email = new email_template;
$email->send('invoice_due_user', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$cyid]["iso"], $C_list->date($invoice->fields['due_date']));
} elseif ($PLG->type == 'other') {
$email = new email_template;
$email->send('admin->invoice_due_admin', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$billed_currency_id]["iso"], $C_list->date($invoice->fields['due_date']));
}
}
# increment notice counter
$sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET
notice_count = ' . $db->qstr($invoice->fields['notice_count']+1) . ',
notice_next_date = ' . $db->qstr(time()+86400*3) . '
WHERE
id = ' . $db->qstr($invoice->fields['id']) . ' AND
site_id = ' . $db->qstr(DEFAULT_SITE);
$db->Execute($sql);
}
else
{
# send service cancelation notice
$email = new email_template;
$email->send('service_suspend_user', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],$cyid), $C_list->date($invoice->fields['due_date']));
$email = new email_template;
$email->send('admin->service_suspend_admin', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],''), $C_list->date($invoice->fields['due_date']));
# overdue - cancel services
$vara['id'] = $invoice->fields['id'];
$this->pVoid($invoice->fields['id']);
# suspend billing activity
$sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET
notice_count = ' . $db->qstr($invoice->fields['notice_count']+1) . ',
suspend_billing = ' . $db->qstr('1') . '
WHERE
id = ' . $db->qstr($invoice->fields['id']) . ' AND
site_id = ' . $db->qstr(DEFAULT_SITE);
$db->Execute($sql);
}
}
else
{
# update billing stauts
$sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET
notice_count = ' . $db->qstr($invoice->fields['notice_count']+1) . ',
billing_status = ' . $db->qstr('1') . ',
billed_amt = ' . $db->qstr($billed_amt) . ',
actual_billed_amt = ' . $db->qstr($actual_billed_amt) . '
WHERE
id = ' . $db->qstr($invoice->fields['id']) . ' AND
site_id = ' . $db->qstr(DEFAULT_SITE);
$db->Execute($sql);
# update invoice via autoapproveInvoice
$this->autoApproveInvoice($invoice->fields['id']);
# User alert of payment processed
$email = new email_template;
$email->send('invoice_paid_user', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$cyid]['iso'], '');
# Admin alert of payment processed
$email = new email_template;
$email->send('admin->invoice_paid_admin', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$billed_currency_id]['iso'], '');
}
$invoice->MoveNext();
unset($PLG);
/* finish transaction */
$db->CompleteTrans();
}
}
/**
* Find out if a user has unpaid invoices
*/
public function has_unpaid($VAR) {
global $smarty, $C_list;
if (! SESS_LOGGED)
return false;
$total = 0;
foreach ($this->sInvoicesBal(SESS_ACCOUNT) as $details)
$total += $details['balance'];
if ($total)
$smarty->assign('has_unpaid',$C_list->format_currency_num($total,SESS_CURRENCY));
}
/**
* Get the totals for multiple invoices or for a group of invoices stored in temp
*
* The retrieved invoice numbers are stored in $this->invoice;
*
* @param string If invoice is 'MULTI-*', get unpaid invoices from the temporary_data table, otherwise
* get the unpaid invoices listed, or all invoices if blank.
*/
private function multiple_invoice_total($invoice,$account_id=SESS_ACCOUNT) {
$this->invoice = array();
$db = &DB();
if (! preg_match('/^MULTI-/',$invoice)) {
$id_list='';
if ($invoice = preg_replace('/,$/','',$invoice))
$id_list = sprintf('id in (%s) AND',$invoice);
# Get invoice totals
$total = 0;
$rs = $db->Execute(sqlSelect($db,'invoice','id,total_amt,billed_amt,credit_amt',sprintf('%s account_id=%s AND billing_status=0 AND (refund_status=0 OR refund_status IS NULL) AND status=1',$id_list,SESS_ACCOUNT)));
if ($rs && $rs->RecordCount()) {
while (! $rs->EOF) {
$this->invoice[$rs->fields['id']] = $rs->fields['total_amt']-$rs->fields['billed_amt']-$rs->fields['credit_amt'];
$total += $this->invoice[$rs->fields['id']];
$rs->MoveNext();
}
return $total;
}
} else {
# Get total from temp data
$rs = $db->Execute(sqlSelect($db,'temporary_data','data,field1',array('field2'=>$invoice)));
if ($rs && $rs->RecordCount() && $rs->fields['field1'] > 0) {
$this->invoice = unserialize($rs->fields['data']);
return $rs->fields['field1'];
}
}
return false;
}
/**
* Preview checkout of multiple invoices
*/
public function tpl_checkout_multiple_preview($VAR) {
global $smarty,$C_list;
if (! SESS_LOGGED)
return false;
# If the ID is blank, this will get all unpaid invoices.
if (! isset($VAR['id']))
$VAR['id'] = '';
$total = $this->multiple_invoice_total($VAR['id'],SESS_ACCOUNT);
$db = &DB();
if ($total > 0 && count($this->invoice) > 1) {
# Get country id for checkout options
$account = $db->Execute(sqlSelect($db,'account','country_id',array('id'=>SESS_ACCOUNT)));
# Get payment options
include_once(PATH_MODULES.'checkout/checkout.inc.php');
$checkout = new checkout;
$checkoutoptions = $checkout->get_checkout_options(SESS_ACCOUNT,$total,false,true);
# Get a temporary id (48 hours)
$id = sqlGenID($db,'temporary_data');
$invoice['id'] = sprintf('MULTI-%s',$id);
$invoice['total'] = $total;
$fields = array('date_orig'=>time(),'date_expire'=>time()+86400*3,'field2'=>$invoice['id'],'field1'=>$total,'data'=>serialize($this->invoice));
$rs = $db->Execute($q=sqlInsert($db,'temporary_data',$fields,$id));
$smarty->assign('record',$invoice);
$smarty->assign('total',$C_list->format_currency_num($total,SESS_CURRENCY));
$smarty->assign('checkoutoptions',$checkoutoptions);
} elseif (count($this->invoice) == 1) {
printf("<script language=javascript>document.location.href='?_page=invoice:user_view&id=%s';</script>",key($this->invoice));
} else {
echo _('No due invoices selected for payment.');
}
}
/**
* Make a payment now
*/
public function checkoutnow($VAR) {
global $C_translate,$smarty,$C_list,$VAR;
# Validate user logged in:
if (SESS_LOGGED != '1') {
echo '<script type="text/javascript">alert("You must be logged in to complete this purchase! Please refresh this page in your browser to login now...");</script>';
return false;
}
# If the ID is blank, this will get all unpaid invoices.
if (! isset($VAR['invoice_id']))
return false;
# Some defaults
$recur_amt = 0;
$db = &DB();
if(preg_match('/^MULTI-/',@$VAR['invoice_id'])) {
# Get multi-invoice details
$total = $this->multiple_invoice_total($VAR['invoice_id'],SESS_ACCOUNT);
if (! $total)
return false;
$recur_arr = false;
$account_id = SESS_ACCOUNT;
$this->invoice_id = $VAR['invoice_id'];
$CURRENCY = DEFAULT_CURRENCY;
$multi = true;
} else {
# Validate the invoice selected, & get the totals:
$result = $db->Execute($q=sqlSelect($db,'invoice','*',array('id'=>$VAR['invoice_id'])));
if (! $result || $result->RecordCount() == 0)
return false;
# Determine the price & currency
if ($result->fields['billed_currency_id'] != $result->fields['actual_billed_currency_id']) {
global $C_list;
$CURRENCY = $result->fields['actual_billed_currency_id'];
if($result->fields['billed_amt'] <= 0)
$total = $C_list->format_currency_decimal($result->fields['total_amt'],$CURRENCY);
else
$total = $C_list->format_currency_decimal($result->fields['total_amt'],$CURRENCY)-$result->fields['actual_billed_amt'];
} else {
$CURRENCY = $result->fields['billed_currency_id'];
$total = $result->fields['total_amt']-$result->fields['billed_amt'];
}
if ($result->fields['recur_amt'] > 0)
$recur_amt = $C_list->format_currency_decimal($result->fields['recur_amt'],$CURRENCY);
@$recur_arr = unserialize($result->fields['recur_arr']);
$account_id = $result->fields['account_id'];
$this->invoice_id = $result->fields['id'];
$this->invoice[$result->fields['id']] = $total;
$multi = false;
}
$amount = round($total, 2);
# Get the account details:
$sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'account WHERE site_id = ' . $db->qstr(DEFAULT_SITE) . ' AND id = ' . $db->qstr($account_id);
$account = $db->Execute($sql);
if (!$account || !$account->RecordCount()) return false;
# Validate checkout option selected is allowed for purchase:
$q = "SELECT * FROM ".AGILE_DB_PREFIX."checkout WHERE site_id = ".$db->qstr(DEFAULT_SITE)." AND id = ".$db->qstr(@$VAR['option'])." AND active = 1 AND ";
if($recur_amt>0 && @$billed_amt == 0) $q .= "allow_recurring = 1 "; else $q .= "allow_new = 1 ";
$chopt = $db->Execute($q);
if (!$chopt || !$chopt->RecordCount()) return false;
if($chopt && $chopt->RecordCount()) {
$show = true;
if ( @$chopt->fields["total_maximum"] != "" && $total > $chopt->fields["total_maximum"] ) $show = false;
if ( @$chopt->fields["total_miniumum"] != "" && $total < $chopt->fields["total_miniumum"] ) $show = false;
}
if(!$show) {
echo '<script language=Javascript> alert("Unable to checkout with the selected method, please select another."); </script> ';
return false;
}
# Load the checkout plugin:
$plugin_file = PATH_PLUGINS . 'checkout/'. $chopt->fields["checkout_plugin"] . '.php';
include_once ( $plugin_file );
eval ( '$PLG = new plg_chout_' . $chopt->fields["checkout_plugin"] . '("'.@$VAR["option"].'",$multi);');
if(!empty($VAR['account_billing_id']) && @$VAR['new_card']==2) {
/* validate credit card on file details */
$account_billing_id=$VAR['account_billing_id'];
if(!$PLG->setBillingFromDB($account_id, $account_billing_id, $VAR['option'])) {
global $C_debug;
$C_debug->alert("Sorry, we cannot use that billing record for this purchase.");
return false;
}
} else {
/* use passed in vars */
$PLG->setBillingFromParams($VAR);
}
# Set Invoice Vars:
$this->total_amt = $amount;
$this->currency_iso = $C_list->currency_iso($CURRENCY);
$this->currency_iso_admin = $C_list->currency_iso($CURRENCY);
$this->account_id = $account_id;
$this->actual_billed_currency_id = $CURRENCY;
$this->billed_currency_id = $CURRENCY;
$this->checkout_plugin_id = @$VAR["option"];
# Run the plugin bill_checkout() method:
$this->checkout_plugin_data = $PLG->bill_checkout($amount, $this->invoice_id, $this->currency_iso, $account->fields, $recur_amt, $recur_arr,$this->invoice);
# redirect
if(!empty($this->checkout_plugin_data['redirect'])) echo $this->checkout_plugin_data['redirect'];
# determine results
if( $this->checkout_plugin_data === false ) {
if(!empty($PLG->redirect)) echo $PLG->redirect;
return false;
} elseif ($PLG->type == "gateway" && empty($PLG->redirect)) {
if(empty($this->admin_checkout)) {
$VAR['_page'] = "invoice:thankyou";
} else {
$VAR['_page'] = "invoice:view";
}
} elseif ($PLG->type == "redirect") {
echo "<html><head></head><body><center>
Please wait while we redirect you to the secure payment site....
{$PLG->redirect}</center></body></html>";
}
# Call the Plugin method for storing the checkout data, if new data entered:
$this->account_billing_id = $PLG->store_billing($VAR, $PLG);
# Load the email template module
include_once(PATH_MODULES.'email_template/email_template.inc.php');
$mail = new email_template;
# Update billing details for this invoice, if realtime billing succeeded:
if($PLG->type == 'gateway' || $amount == 0) {
$q = "UPDATE ".AGILE_DB_PREFIX."invoice
SET
account_billing_id = " .$db->qstr($this->account_billing_id). ",
billing_status = " .$db->qstr(1). ",
billed_amt = " .$db->qstr($total). ",
actual_billed_amt = " .$db->qstr($amount). ",
date_last = " .$db->qstr(time()). ",
checkout_plugin_id = " .$db->qstr($this->checkout_plugin_id) .",
checkout_plugin_data = " .$db->qstr(serialize($this->checkout_plugin_data)). "
WHERE
site_id = ".$db->qstr(DEFAULT_SITE)." AND
id = ".$db->qstr($this->invoice_id);
$rst = $db->Execute($q);
if ($rst === false) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
// loop through each invoice paid
foreach($this->invoice as $this->invoice_id) {
# Send billed e-mail notice to user
$email = new email_template;
$email->send('invoice_paid_user', $this->account_id, $this->invoice_id, $this->currency_iso, '');
# Admin alert of payment processed
$email = new email_template;
$email->send('admin->invoice_paid_admin', $this->account_id, $this->invoice_id, $this->currency_iso_admin, '');
# Submit the invoice for approval
$arr['id'] = $this->invoice_id;
$this->pApprove($this->invoice_id);
}
} else {
# Just update the last_date and plugin data
$q = "UPDATE ".AGILE_DB_PREFIX."invoice
SET
account_billing_id = " .$db->qstr($this->account_billing_id). ",
date_last = " .$db->qstr(time()). ",
checkout_plugin_id = " .$db->qstr($this->checkout_plugin_id) .",
checkout_plugin_data = " .$db->qstr(serialize($this->checkout_plugin_data)). "
WHERE
site_id = ".$db->qstr(DEFAULT_SITE)." AND
id = ".$db->qstr($this->invoice_id);
$rst = $db->Execute($q);
if ($rst === false) {
global $C_debug;
$C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg());
return false;
}
# Admin e-mail alert of manual payment processing
if ( $PLG->getName() == 'MANUAL' ) {
$date_due = $C_list->date(time());
foreach($this->invoice as $this->invoice_id) {
$email = new email_template;
$email->send('admin->invoice_due_admin', $this->account_id, $this->invoice_id, '', $date_due);
}
global $C_debug;
$C_debug->alert($C_translate->translate('manual_alert','checkout'));
}
}
}
/** GENERIC INVOICE METHODS **/
/**
* Return a list of accounts with their current outsanding invoices balance
*
* @return array List of Accounts and their current balance
*/
public function sAccountsBal() {
static $sAccountBal = array();
if (! count($sAccountBal)) {
$db = &DB();
$rs = $db->Execute(sqlSelect($db,'invoice','account_id,ROUND(SUM(total_amt-billed_amt-IFNULL(credit_amt,0)),2) AS balance',false,'account_id','','','account_id'));
if ($rs && $rs->RecordCount()) {
while (! $rs->EOF) {
$sAccountBal[$rs->fields['account_id']] = $rs->fields['balance'];
$rs->MoveNext();
}
}
}
return $sAccountBal;
}
/**
* Get a list of invoices with a non-zero balance
*
* @param $account_id Get the invoices for this account, otherwise all invoices are returned.
* @param $refresh If true, force re-reading database to get the list of invoices.
* @return array List of invoices with balance
*/
public function sInvoicesBal($account_id=null,$refresh=false) {
static $sInvoicesBal = array();
if ($refresh || ! count($sInvoicesBal)) {
$sInvoicesBal = array();
$db = &DB();
$rs = $db->Execute(sqlSelect('invoice','date_orig,account_id,id,total_amt,billed_amt,IFNULL(credit_amt,0) as credit_amt,ROUND(total_amt-billed_amt-IFNULL(credit_amt,0),2) as balance',
array('where'=>'(refund_status=0 OR refund_status IS NULL) AND status=1 AND total_amt-billed_amt-IFNULL(credit_amt,0)!=0','orderby'=>'account_id,date_orig,id')));
if ($rs && $rs->RecordCount()) {
while (! $rs->EOF) {
$invoice = array();
$invoice['invoice_id'] = $rs->fields['id'];
$invoice['balance'] = $rs->fields['balance'];
$invoice['total_amt'] = $rs->fields['total_amt'];
$invoice['billed_amt'] = $rs->fields['billed_amt'];
$invoice['credit_amt'] = $rs->fields['credit_amt'];
$invoice['date_orig'] = $rs->fields['date_orig'];
$sInvoicesBal[$rs->fields['account_id']][$rs->fields['id']] = $invoice;
$rs->MoveNext();
}
}
}
return (is_null($account_id) ? $sInvoicesBal : isset($sInvoicesBal[$account_id]) ? $sInvoicesBal[$account_id] : array());
}
/**
* Get a list of invoices for an account.
*
* @param $account_id
* @param $invoices Optional array of invoices to retrieve, otherwise all invoices are return.
* @return array Requested invoices
*/
public function sInvoicesAcc($account_id,$invoices=array()) {
static $sInvoicesAccount = array();
$return = array();
if (count($invoices)) {
foreach ($invoices as $invoice)
if (isset($sInvoicesAccount[$account_id][$invoice_id]))
$return[$invoice_id] = $sInvoicesAccount[$account_id][$invoice_id];
} else {
if (isset($sInvoicesAccount[$account_id]))
$return = $sInvoicesAccount[$account_id];
}
# Do we need to get the invoices from the DB?
if ((! count($invoices) && ! count($return)) || count($invoices) != count($return)) {
$db = &DB();
if (count($invoices))
$where = sprintf('AND id IN (%s)',join(',',$invoices));
else
$where = '';
$rs = $db->Execute(sqlSelect('invoice','id,date_orig,total_amt,billed_amt,IFNULL(credit_amt,0) as credit_amt,ROUND(total_amt-billed_amt-IFNULL(credit_amt,0),2) AS balance',array('where'=>sprintf('account_id=%s %s',$account_id,$where),'orderby'=>'id')));
if ($rs && $rs->RecordCount()) {
while (! $rs->EOF) {
$sInvoicesAccount[$account_id][$rs->fields['id']] = $rs->fields;
$return[$rs->fields['id']] = $rs->fields;
$rs->MoveNext();
}
}
}
return $return;
}
public function invoice_days() { return $this->sInvoiceDays(); }
/**
* Determine the number of days in advance an invoice should be generated.
* Invoices are generated when the greater of:
* + system default (setup:max_inv_gen_period), (to be deprecated)
* + system default (setup_invoice:invoice_advance_gen),
*
* @return int Days in Advance to Issue Invoices.
*/
public function sInvoiceDays() {
$db = &DB();
# Get the max invoice days from the setup_invoice table
$days = 0;
# First from the setup table.
$setup = $db->Execute(sqlSelect($db,'setup','max_inv_gen_period',''));
if (isset($setup->fields['max_inv_gen_period']))
$days = $setup->fields['max_inv_gen_period'];
# Then from the setup_invoice table.
$setup = $db->Execute(sqlSelect($db,'setup_invoice','invoice_advance_gen,advance_notice',''));
if (isset($setup->fields['invoice_advance_gen']) && $setup->fields['invoice_advance_gen'] > $days)
$days = $setup->fields['invoice_advance_gen'];
if (isset($setup->fields['advance_notice']) && $setup->fields['advance_notice'] > $days)
$days = $setup->fields['advance_notice'];
return $days;
}
}
?>