3499 lines
113 KiB
PHP
3499 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,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})
|
|
%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> </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> </td></tr>';
|
|
|
|
$return .= sprintf('<tr><td colspan="2" style="width: 50%%";> </td><td>%s</td><td style="text-align: right;"><b>%s</b></td></tr>',
|
|
_('Tax Invoice'),$this->getPrintInvoiceNum());
|
|
$return .= sprintf('<tr><td colspan="2"> </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"> </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"> </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> </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> </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> </td></tr>';
|
|
if ($this->print['invoice']['discount_amt'])
|
|
$return .= sprintf('<tr style="font-size: 80%%;"><td> </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> </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> </td></tr>';
|
|
|
|
$return .= sprintf('<tr><td> </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;
|
|
}
|
|
}
|
|
?>
|