Esempio n. 1
0
 function generate_pdf()
 {
     log_debug("invoice", "Executing generate_pdf()");
     // load data if required
     if (!is_array($this->invoice_fields)) {
         $this->load_data();
         $this->load_data_export();
     }
     // start the PDF object
     //
     // note: the & allows decontructors to operate
     //       Unfortunatly this trick is now deprecated with PHP 5.3.x and creates unsilencable errors ~JC 20100110
     //
     // get template filename based on currently selected options
     $template_data = sql_get_singlerow("SELECT `template_type`, `template_file` FROM templates WHERE template_type IN('" . $this->type . "_invoice_tex', '" . $this->type . "_invoice_htmltopdf') AND active='1' LIMIT 1");
     //exit("<pre>".print_r($template_data, true)."</pre>");
     switch ($template_data['template_type']) {
         case $this->type . '_invoice_htmltopdf':
             $this->obj_pdf = new template_engine_htmltopdf();
             $template_file = $template_data['template_file'] . "/index.html";
             if (is_dir("../../{$template_data['template_file']}")) {
                 $this->obj_pdf->set_template_directory("../../{$template_data['template_file']}");
             } else {
                 $this->obj_pdf->set_template_directory("../{$template_data['template_file']}");
             }
             break;
         case $this->type . '_invoice_tex':
         default:
             $this->obj_pdf = new template_engine_latex();
             $template_file = $template_data['template_file'] . ".tex";
             break;
     }
     if (!$template_file) {
         // fall back to old version
         //
         // TODO: we can remove this fallback code once the new templating system is fully implemented, this is to
         // just make everything work whilst stuff like quote templates are being added.
         //
         $template_file = "templates/latex/" . $this->type . "_invoice";
     }
     // load template
     if (file_exists("../../{$template_file}")) {
         $this->obj_pdf->prepare_load_template("../../{$template_file}");
     } elseif (file_exists("../{$template_file}")) {
         $this->obj_pdf->prepare_load_template("../{$template_file}");
     } else {
         // if we can't find the template file, then something is rather wrong.
         log_write("error", "invoice", "Unable to find template file {$template_file}, currently running in directory " . getcwd() . ", fatal error.");
         return 0;
     }
     /*
     	Company Data
     */
     // company logo
     $this->obj_pdf->prepare_add_file("company_logo", "png", "COMPANY_LOGO", 0);
     /*
     	Previous Activity
     
     	Some invoice PDFs include a "previous activity" statement function displaying past account activity - we only display unpaid
     	invoices.
     */
     $structure_pastactivity = array();
     $structure_pastactivity[0] = array();
     // reserved for past balance
     $amount_outstanding = sql_get_singlevalue("SELECT SUM(amount_total - amount_paid) as value FROM account_ar WHERE customerid='" . $this->data["customerid"] . "' AND id!='" . $this->id . "' AND date_trans <= '" . $this->data["date_trans"] . "'");
     $amount_outstanding_past = $amount_outstanding;
     $sql_past_obj = new sql_query();
     $sql_past_obj->string = "SELECT id, code_invoice, date_trans, amount_total, amount_paid, date_trans FROM account_ar WHERE customerid='" . $this->data["customerid"] . "' AND id!='" . $this->id . "' AND date_trans <= '" . $this->data["date_trans"] . "' ORDER BY date_trans DESC LIMIT 2";
     $sql_past_obj->execute();
     if ($sql_past_obj->num_rows()) {
         $sql_past_obj->fetch_array();
         foreach ($sql_past_obj->data as $data_row) {
             // invoice
             $itemdata = array();
             $itemdata["item_date_raw"] = time_date_to_timestamp($data_row["date_trans"]) . "." . $data_row["id"] . "00";
             // used to sort items
             $itemdata["item_date"] = time_format_humandate($data_row["date_trans"]);
             $itemdata["item_details"] = "Invoice " . $data_row["code_invoice"] . "";
             $itemdata["item_amount"] = format_money($data_row["amount_total"]);
             $structure_pastactivity[] = $itemdata;
             // payments (if any)
             if ($data_row["amount_paid"] > 0) {
                 $sql_pay_obj = new sql_query();
                 $sql_pay_obj->string = "SELECT id, amount FROM account_items WHERE invoiceid='" . $data_row["id"] . "' AND type='payment'";
                 $sql_pay_obj->execute();
                 $sql_pay_obj->fetch_array();
                 foreach ($sql_pay_obj->data as $data_pay) {
                     // update balance
                     $amount_outstanding_past = $amount_outstanding_past + $data_pay["amount"];
                     // source & date
                     $pay_date = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $data_pay["id"] . "' AND option_name='DATE_TRANS' LIMIT 1");
                     $pay_credit = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $data_pay["id"] . "' AND option_name='CREDIT' LIMIT 1");
                     // add payment item
                     $itemdata = array();
                     $itemdata["item_date_raw"] = time_date_to_timestamp($pay_date) . "." . $data_row["id"] . "01";
                     // used to sort items
                     $itemdata["item_date"] = time_format_humandate($pay_date);
                     if ($pay_credit) {
                         $itemdata["item_details"] = "Credit applied to invoice " . $data_row["code_invoice"] . "";
                     } else {
                         $itemdata["item_details"] = "Payment against invoice " . $data_row["code_invoice"] . "";
                     }
                     $itemdata["item_amount"] = "-" . format_money($data_pay["amount"]);
                     $structure_pastactivity[] = $itemdata;
                 }
                 unset($sql_pay_obj);
             }
             $amount_outstanding_past = $amount_outstanding_past - $data_row["amount_total"];
         }
         // sort by date, to correct payment & invoice ordering
         if (!function_exists("cmp_date")) {
             function cmp_date($a, $b)
             {
                 if ($a["item_date_raw"] == $b["item_date_raw"]) {
                     return 0;
                 }
                 return $a["item_date_raw"] < $b["item_date_raw"] ? -1 : 1;
             }
         }
         usort($structure_pastactivity, "cmp_date");
         // add previous balance item
         $structure_pastactivity[0]["item_date"] = "Previous Balance";
         $structure_pastactivity[0]["item_details"] = "";
         $structure_pastactivity[0]["item_amount"] = format_money($amount_outstanding_past);
     } else {
         $itemdata = array();
         $itemdata["item_date"] = time_format_humandate(date("Y-m-d"));
         $itemdata["item_details"] = "No Past Activity";
         $itemdata["item_amount"] = "";
         $structure_pastactivity[0] = $itemdata;
         $amount_outstanding = "0.00";
     }
     $this->obj_pdf->prepare_add_array("previous_items", $structure_pastactivity);
     $this->obj_pdf->prepare_add_field("amount_outstanding", format_money($amount_outstanding));
     unset($structure_pastactivity);
     unset($sql_past_obj);
     /*
     	Add general invoice details from load_data_export	
     */
     $this->invoice_fields["amount_total"] = format_money($this->data["amount_total"] - $this->data["amount_paid"]);
     $this->invoice_fields["amount_total_final"] = format_money($this->data["amount_total"] - $this->data["amount_paid"] + $amount_outstanding);
     foreach ($this->invoice_fields as $invoice_field_key => $invoice_field_value) {
         $this->obj_pdf->prepare_add_field($invoice_field_key, $invoice_field_value);
     }
     /*
     	Invoice Items
     	(excluding tax items - these need to be processed in a different way)
     */
     // fetch invoice items
     $sql_items_obj = new sql_query();
     $sql_items_obj->string = "SELECT " . "id, type, chartid, customid, quantity, units, amount, price, description " . "FROM account_items " . "WHERE invoiceid='" . $this->id . "' " . "AND invoicetype='" . $this->type . "' " . "AND type!='tax' " . "AND type!='payment' " . "ORDER BY type, customid, chartid, description";
     $sql_items_obj->execute();
     $sql_items_obj->fetch_array();
     $structure_invoiceitems = array();
     $structure_group_summary = array();
     foreach ($sql_items_obj->data as $itemdata) {
         $structure = array();
         $structure["quantity"] = $itemdata["quantity"];
         switch ($itemdata["type"]) {
             case "product":
                 /*
                 	Fetch product code
                 */
                 $sql_obj = new sql_query();
                 $sql_obj->string = "SELECT code_product FROM products WHERE id='" . $itemdata["customid"] . "' LIMIT 1";
                 $sql_obj->execute();
                 $sql_obj->fetch_array();
                 $structure["info"] = $sql_obj->data[0]["code_product"];
                 unset($sql_obj);
                 /*
                 	Fetch discount (if any)
                 */
                 $itemdata["discount"] = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $itemdata["id"] . "' AND option_name='DISCOUNT'");
                 /*
                 	Calculate Amount
                 
                 	(Amount field already has discount removed, but we can't use this for export, since we want the line item to be the full
                 	 amount, with an additional line item for the discount)
                 */
                 $itemdata["amount"] = $itemdata["price"] * $itemdata["quantity"];
                 $sql_obj = new sql_query();
                 $sql_obj->string = "SELECT product_groups.group_name " . " FROM products " . " LEFT JOIN product_groups " . " ON product_groups.id = products.id_product_group " . " WHERE products.id = '" . $itemdata["customid"] . "' " . " LIMIT 1";
                 $sql_obj->execute();
                 $sql_obj->fetch_array();
                 if ($sql_obj->data[0]["group_name"] != null) {
                     $structure["group"] = $sql_obj->data[0]["group_name"];
                 } else {
                     $structure["group"] = lang_trans("group_products");
                 }
                 break;
             case "time":
                 /*
                 	Fetch time group ID
                 */
                 $groupid = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $itemdata["id"] . "' AND option_name='TIMEGROUPID'");
                 $structure["info"] = sql_get_singlevalue("SELECT CONCAT_WS(' -- ', projects.code_project, time_groups.name_group) as value FROM time_groups LEFT JOIN projects ON projects.id = time_groups.projectid WHERE time_groups.id='{$groupid}' LIMIT 1");
                 /*
                 	Fetch discount (if any)
                 */
                 $itemdata["discount"] = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $itemdata["id"] . "' AND option_name='DISCOUNT'");
                 /*
                 	Calculate Amount
                 
                 	(Amount field already has discount removed, but we can't use this for export, since we want the line item to be the full
                 	 amount, with an additional line item for the discount)
                 */
                 $itemdata["amount"] = $itemdata["price"] * $itemdata["quantity"];
                 $structure["group"] = lang_trans("group_time");
                 break;
             case "service":
             case "service_usage":
                 /*
                 	Fetch Service Name
                 */
                 $sql_obj = new sql_query();
                 $sql_obj->string = "SELECT name_service FROM services WHERE id='" . $itemdata["customid"] . "' LIMIT 1";
                 $sql_obj->execute();
                 $sql_obj->fetch_array();
                 $structure["info"] = $sql_obj->data[0]["name_service"];
                 unset($sql_obj);
                 /*
                 	Fetch discount (if any)
                 */
                 $itemdata["discount"] = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $itemdata["id"] . "' AND option_name='DISCOUNT'");
                 /*
                 	Calculate Amount
                 
                 	(Amount field already has discount removed, but we can't use this for export, since we want the line item to be the full
                 	 amount, with an additional line item for the discount)
                 */
                 $itemdata["amount"] = $itemdata["price"] * $itemdata["quantity"];
                 /*
                 	Fetch CDR group if any
                 */
                 $itemdata["CDR_BILLGROUP"] = sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $itemdata["id"] . "' AND option_name='CDR_BILLGROUP'");
                 /*
                 	Set the service group
                 
                 	This is used for layout and titling purposes on the invoice - there are several options, we need to fetch the service group depending
                 	on whether the service item is a plan or usage item and specific service types might have other group conditions, eg CDR_BILLGROUP.
                 */
                 $sql_obj = new sql_query();
                 if ($itemdata["type"] == "service_usage") {
                     if ($itemdata["CDR_BILLGROUP"]) {
                         $sql_obj->string = "SELECT CONCAT_WS(' ', billgroup_name, 'Call Charges') as group_name FROM cdr_rate_billgroups WHERE id='" . $itemdata["CDR_BILLGROUP"] . "' LIMIT 1";
                     } else {
                         $sql_obj->string = "SELECT service_groups.group_name FROM services LEFT JOIN service_groups ON service_groups.id = services.id_service_group_usage WHERE services.id = '" . $itemdata["customid"] . "' LIMIT 1";
                     }
                 } else {
                     $sql_obj->string = "SELECT service_groups.group_name FROM services LEFT JOIN service_groups ON service_groups.id = services.id_service_group WHERE services.id = '" . $itemdata["customid"] . "' LIMIT 1";
                 }
                 $sql_obj->execute();
                 $sql_obj->fetch_array();
                 $structure["group"] = $sql_obj->data[0]["group_name"];
                 unset($sql_obj);
                 break;
             case "standard":
                 /*
                 	Fetch account name and blank a few fields
                 */
                 $sql_obj = new sql_query();
                 $sql_obj->string = "SELECT CONCAT_WS(' -- ',code_chart,description) as name_account FROM account_charts WHERE id='" . $itemdata["chartid"] . "' LIMIT 1";
                 $sql_obj->execute();
                 $sql_obj->fetch_array();
                 $structure["info"] = $sql_obj->data[0]["name_account"];
                 $structure["quantity"] = " ";
                 $itemdata["price"] = NULL;
                 $structure["group"] = lang_trans("group_other");
                 $structure["group_num"] = "1";
                 unset($sql_obj);
                 break;
         }
         // define group summary values
         if (!isset($structure_group_summary[$structure["group"]])) {
             $structure_group_summary[$structure["group"]] = 0;
         }
         $structure_group_summary[$structure["group"]] += $itemdata["amount"];
         // finalise item
         $structure["description"] = trim($itemdata["description"]);
         $structure["units"] = $itemdata["units"];
         if ($itemdata["price"]) {
             $structure["price"] = format_money($itemdata["price"]);
         } else {
             $structure["price"] = "";
         }
         $structure["amount"] = format_money($itemdata["amount"]);
         $structure_invoiceitems[] = $structure;
         // if a discount exists, then we add an additional item row for the discount
         if (!empty($itemdata["discount"])) {
             $structure["description"] = "Discount of " . $itemdata["discount"] . "%";
             $structure["quantity"] = "";
             $structure["units"] = "";
             $structure["price"] = "";
             // work out the discount amount to remove
             $discount_calc = $itemdata["discount"] / 100;
             $discount_calc = $itemdata["amount"] * $discount_calc;
             $structure["amount"] = "-" . format_money($discount_calc);
             // track for summary report
             if (!isset($structure_group_summary["group_discount"])) {
                 $structure_group_summary["group_discount"] = $discount_calc;
             } else {
                 $structure_group_summary["group_discount"] += $discount_calc;
             }
             // add extra line item
             $structure_invoiceitems[] = $structure;
         }
     }
     foreach ($structure_invoiceitems as $invoice_item) {
         $invoice_items_by_group[$invoice_item['group']][] = $invoice_item;
     }
     ksort($invoice_items_by_group);
     if (count($invoice_items_by_group) > 1) {
         $structure_invoiceitems = array();
         foreach ($invoice_items_by_group as $invoice_item_set) {
             $structure_invoiceitems = array_merge($structure_invoiceitems, $invoice_item_set);
         }
     }
     //exit("<pre>".print_r($structure_invoiceitems,true)."</pre>");
     $this->obj_pdf->prepare_add_array("invoice_items", $structure_invoiceitems);
     unset($sql_items_obj);
     /*
     	Tax Items
     */
     // fetch tax items
     $sql_tax_obj = new sql_query();
     $sql_tax_obj->string = "SELECT " . "account_items.amount, " . "account_taxes.name_tax, " . "account_taxes.taxnumber " . "FROM " . "account_items " . "LEFT JOIN account_taxes ON account_taxes.id = account_items.customid " . "WHERE " . "invoiceid='" . $this->id . "' " . "AND invoicetype='" . $this->type . "' " . "AND type='tax'";
     $sql_tax_obj->execute();
     if ($sql_tax_obj->num_rows()) {
         $sql_tax_obj->fetch_array();
         $structure_taxitems = array();
         foreach ($sql_tax_obj->data as $taxdata) {
             $structure = array();
             $structure["name_tax"] = $taxdata["name_tax"];
             $structure["taxnumber"] = $taxdata["taxnumber"];
             $structure["amount"] = format_money($taxdata["amount"]);
             $structure_taxitems[] = $structure;
         }
     }
     $this->obj_pdf->prepare_add_array("taxes", $structure_taxitems);
     /*
     	Payment Items
     */
     // fetch payment items
     $sql_payment_obj = new sql_query();
     $sql_payment_obj->string = "SELECT id, amount, description FROM account_items WHERE invoiceid='" . $this->id . "' AND invoicetype='" . $this->type . "' AND type='payment'";
     $sql_payment_obj->execute();
     $structure_payments = array();
     if ($sql_payment_obj->num_rows()) {
         $sql_payment_obj->fetch_array();
         foreach ($sql_payment_obj->data as $itemdata) {
             $structure = array();
             if (sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE itemid='" . $itemdata["id"] . "' AND option_name='CREDIT' LIMIT 1")) {
                 $structure["label"] = "Credit Applied";
             } else {
                 $structure["label"] = "Payment";
             }
             $structure["amount"] = format_money($itemdata["amount"]);
             $structure["date"] = time_format_humandate(sql_get_singlevalue("SELECT option_value as value FROM account_items_options WHERE option_name='DATE_TRANS' AND itemid='" . $itemdata["id"] . "' LIMIT 1"));
             $structure_payments[] = $structure;
         }
     }
     $this->obj_pdf->prepare_add_array("invoice_payments", $structure_payments);
     /*
     	Group Summaries
     
     	Some invoice templates have a summary header page with totals for each group type. Users can
     	then read through for full item listings and details.
     */
     $stucture_group_summary_final = array();
     // add all non-discount groups
     foreach (array_keys($structure_group_summary) as $group_name) {
         if ($group_name != 'group_discount') {
             $structure = array();
             $structure["group_name"] = $group_name;
             $structure["group_amount"] = format_money($structure_group_summary[$group_name]);
             $structure_group_summary_final[] = $structure;
         }
     }
     // add discount group last
     if (isset($structure_group_summary["group_discount"]) && $structure_group_summary["group_discount"] > 0) {
         $structure = array();
         $structure["group_name"] = lang_trans('group_discount');
         $structure["group_amount"] = "-" . format_money($structure_group_summary['group_discount']);
         $structure_group_summary_final[] = $structure;
     }
     $this->obj_pdf->prepare_add_array("summary_items", $structure_group_summary_final);
     // depending on the billing options we may adjust template display, ensure default is set first
     $this->obj_pdf->prepare_add_field('billing_default', 1);
     $billing_option = sql_get_singlevalue("SELECT billing_method AS value FROM customers WHERE id='" . $this->data["customerid"] . "'");
     log_write("debug", "inc_invoice", "Billing option for customer is: " . $billing_option);
     switch ($billing_option) {
         case 'direct debit':
             $this->obj_pdf->prepare_add_field('billing_direct_debit', 1);
             $this->obj_pdf->prepare_add_field('billing_default', '');
             break;
         case 'manual':
         default:
             $this->obj_pdf->prepare_add_field('billing_default', 1);
             break;
     }
     /*
     	Output PDF
     */
     // perform string escaping for latex
     $this->obj_pdf->prepare_escape_fields();
     // fillter template data
     $this->obj_pdf->fillter_template_data();
     // fill template
     $this->obj_pdf->prepare_filltemplate();
     /*
     	Debugging Functions
     
     	Debugging invoice generation can be tricky, especially when making large number of
     	development changes. These functions need to be uncommented in code, future releases
     	should make them a checkable option.
     */
     // display invoice in browser, suitable for HTML-based invoices only
     //foreach ($this->obj_pdf->processed as $line)
     //{
     //	$line = str_replace('(tmp_filename)', "../../". $template_data['template_file'] ."/", $line);
     //	print $line;
     //}
     // output raw HTML
     //print "<pre>";
     //print_r($this->obj_pdf->processed);
     //print "</pre>";
     //die("Terminated Generation");
     // generate PDF output
     $this->obj_pdf->generate_pdf();
 }
 function makeCSV()
 {
     // have a go at getting the data if its not there
     if (!is_object($this->sql_obj)) {
         $this->getData();
     }
     // fail if data not found
     if (!is_object($this->sql_obj)) {
         log_write("error", "cdr_csv", __FUNCTION__ . " called but no data present, has the query been performed?");
         return false;
     }
     // fetch customer code information
     $customer_data = sql_get_singlerow("SELECT code_customer, reseller_customer, reseller_id FROM customers WHERE id='" . $this->id_customer . "' LIMIT 1");
     if (!$customer_data) {
         log_write("error", "inc_services_cdr", "No customer with ID " . $this->id_customer . " returned!");
     } else {
         $code_customer = $customer_data["code_customer"];
         // is the customer a member of a reseller? If so, we need the code of the reseller to also use.
         if ($customer_data["reseller_customer"] == "customer_of_reseller") {
             $code_master = sql_get_singlevalue("SELECT code_customer as value FROM customers WHERE id='" . $customer_data["reseller_id"] . "'");
         } else {
             // customer is standalone, use their code as the master
             $code_master = $code_customer;
         }
     }
     // string padding is required as per the following:
     /*
     	  			HEADER SECTION	
     	1	HD-HEADER	Char 6	HEADER	Field contains the following text 'HEADER'
     	2	HD-CUST-NUM	Num 11	Left 0 padded	Master customer account number
     	3	HD-FILE-CREATE-DATE	Char 10	YYYY-MM-DD	Date the file is created
     				
     	BODY SECTION	
     	4	BD-CUST-NBR	Num 11	Left 0 padded	Customer account number
     	5	BD-ORIG-ANI	Num 15	Left 0 padded E164	Originating Party 'A'
     	6	BD-TERM-NBR	Char 16	Left space padded in dialied E164 format	Calling Party 'B'
     	7	BD-START-DT	Char 10	YYYY-MM-DD	Date extracted from the start date
     	8	BD-START-TM	Char 8	HH.MM.SS	Time extracted from the start time
     	9	BD-RATED-SECS	Num 9	Left 0 padded	Rated seconds
     	10	BD-RATED-AMT	Char 10	Left 0 padded before decimal point and right 0 padded after the decimal point XXXXX.XXXX	Rated Dollar amount (excludes all taxes)
     	11	BD-ANS-QUALIFIER	Char 10	As per values in description field	Possible values, ANSWERED, BUSY, FAILED, NO ANSWER
     						
     	TRAILER SECTION	
     	12	TR-TRAILER	Char 7	TRAILER	The following text is in this field
     	13	TR-REC-COUNT	Num 7	Left 0 padded	The number of records in the file, excluding the header and trailer records
     */
     // header of CSV
     $this->csv[] = array('HEADER', str_pad($code_master, 11, "0", STR_PAD_LEFT), date('Y-m-d'));
     if (count($this->sql_obj->data) > 0) {
         $csv_record_count = count($this->sql_obj->data);
         foreach ($this->sql_obj->data as $row) {
             // as such this only currently supports one customer account number
             $row['code_customer'] = $code_customer;
             // reset the row
             $csv_row = array();
             // loop over the csv_body_fields and apply any formating required before adding to the csv_row in the order defined
             foreach ($this->csv_body_fields as $k) {
                 switch ($k) {
                     case 'code_customer':
                         $csv_row[$k] = str_pad($row[$k], 11, "0", STR_PAD_LEFT);
                         break;
                     case 'number_src':
                         $csv_row[$k] = str_pad($row[$k], 11, "0", STR_PAD_LEFT);
                         break;
                     case 'number_dst':
                         $csv_row[$k] = str_pad($row[$k], 15, " ", STR_PAD_LEFT);
                         break;
                     case 'billable_seconds':
                         $csv_row[$k] = str_pad($row[$k], 9, "0", STR_PAD_LEFT);
                         break;
                     case 'price':
                         $csv_row[$k] = trim(money_format('%=0^!#5.4i', $row[$k]));
                         break;
                     default:
                         $csv_row[$k] = $row[$k];
                         break;
                 }
             }
             // add the body row
             $this->csv[] = $csv_row;
         }
     } else {
         $csv_record_count = 0;
     }
     // footer of csv
     $this->csv[] = array('TRAILER', str_pad($csv_record_count, 7, "0", STR_PAD_LEFT));
 }
Esempio n. 3
0
 function generate_pdf()
 {
     log_debug("inc_credits", "Executing generate_pdf()");
     // load data if required
     if (!is_array($this->credit_fields)) {
         $this->load_data();
         $this->load_data_export();
     }
     // start the PDF object
     //
     // note: the & allows decontructors to operate
     //       Unfortunatly this trick is now deprecated with PHP 5.3.x and creates unsilencable errors ~JC 20100110
     //
     // get template filename based on currently selected options
     $template_data = sql_get_singlerow("SELECT `template_type`, `template_file` FROM templates WHERE template_type IN('" . $this->type . "_tex', '" . $this->type . "_htmltopdf') AND active='1' LIMIT 1");
     //exit("<pre>".print_r($template_data, true)."</pre>");
     switch ($template_data['template_type']) {
         case $this->type . '_htmltopdf':
             $this->obj_pdf =& new template_engine_htmltopdf();
             $template_file = $template_data['template_file'] . "/index.html";
             if (is_dir("../../{$template_data['template_file']}")) {
                 $this->obj_pdf->set_template_directory("../../{$template_data['template_file']}");
             } else {
                 $this->obj_pdf->set_template_directory("../{$template_data['template_file']}");
             }
             break;
         case $this->type . '_tex':
         default:
             $this->obj_pdf =& new template_engine_latex();
             $template_file = $template_data['template_file'] . ".tex";
             break;
     }
     if (!$template_file) {
         // fall back to old version
         //
         // TODO: we can remove this fallback code once the new templating system is fully implemented, this is to
         // just make everything work whilst stuff like quote templates are being added.
         //
         $template_file = "templates/latex/" . $this->type . "";
     }
     // load template
     if (file_exists("../../{$template_file}")) {
         $this->obj_pdf->prepare_load_template("../../{$template_file}");
     } elseif (file_exists("../{$template_file}")) {
         $this->obj_pdf->prepare_load_template("../{$template_file}");
     } else {
         // if we can't find the template file, then something is rather wrong.
         log_write("error", "inc_credits", "Unable to find template file {$template_file}, currently running in directory " . getcwd() . ", fatal error.");
         return 0;
     }
     /*
     	Company Data
     */
     // company logo
     $this->obj_pdf->prepare_add_file("company_logo", "png", "COMPANY_LOGO", 0);
     // convert the credit_fields array into
     foreach ($this->credit_fields as $credit_field_key => $credit_field_value) {
         $this->obj_pdf->prepare_add_field($credit_field_key, $credit_field_value);
     }
     /*
     	Fetch credit items (all credit items other than tax, are type == 'credit')
     */
     // fetch invoice items
     $sql_items_obj = new sql_query();
     $sql_items_obj->string = "SELECT " . "id, type, chartid, customid, quantity, units, amount, price, description " . "FROM account_items " . "WHERE invoiceid='" . $this->id . "' " . "AND invoicetype='" . $this->type . "' " . "AND type='credit' " . "ORDER BY customid, chartid, description";
     $sql_items_obj->execute();
     $sql_items_obj->fetch_array();
     $structure_credititems = array();
     foreach ($sql_items_obj->data as $itemdata) {
         $structure = array();
         $structure["info"] = "CREDIT";
         $structure["quantity"] = " ";
         $structure["group"] = lang_trans("group_other");
         $structure["price"] = "";
         $structure["description"] = trim($itemdata["description"]);
         $structure["units"] = $itemdata["units"];
         $structure["amount"] = format_money($itemdata["amount"], 1);
         $structure_credititems[] = $structure;
     }
     //exit("<pre>".print_r($structure_credititems,true)."</pre>");
     $this->obj_pdf->prepare_add_array("credit_items", $structure_credititems);
     unset($sql_items_obj);
     /*
     	Tax Items
     */
     // fetch tax items
     $sql_tax_obj = new sql_query();
     $sql_tax_obj->string = "SELECT " . "account_items.amount, " . "account_taxes.name_tax, " . "account_taxes.taxnumber " . "FROM " . "account_items " . "LEFT JOIN account_taxes ON account_taxes.id = account_items.customid " . "WHERE " . "invoiceid='" . $this->id . "' " . "AND invoicetype='" . $this->type . "' " . "AND type='tax'";
     $sql_tax_obj->execute();
     if ($sql_tax_obj->num_rows()) {
         $sql_tax_obj->fetch_array();
         $structure_taxitems = array();
         foreach ($sql_tax_obj->data as $taxdata) {
             $structure = array();
             $structure["name_tax"] = $taxdata["name_tax"];
             $structure["taxnumber"] = $taxdata["taxnumber"];
             $structure["amount"] = format_money($taxdata["amount"]);
             $structure_taxitems[] = $structure;
         }
     }
     $this->obj_pdf->prepare_add_array("taxes", $structure_taxitems);
     /*
     	Output PDF
     */
     // perform string escaping for latex
     $this->obj_pdf->prepare_escape_fields();
     // fillter template data
     $this->obj_pdf->fillter_template_data();
     // fill template
     $this->obj_pdf->prepare_filltemplate();
     // Useful for debugging - shows the processed template lines BEFORE it is fed to the render engine
     //print "<pre>";
     //print_r($this->obj_pdf->processed);
     //print "</pre>";
     // generate PDF output
     $this->obj_pdf->generate_pdf();
 }
function service_usage_alerts_generate($customerid = NULL)
{
    log_debug("inc_services_usage", "Executing service_usage_alerts_generate({$customerid})");
    /*
    	Fetch configuration Options
    */
    // check that email is enabled
    if (sql_get_singlevalue("SELECT value FROM config WHERE name='EMAIL_ENABLE' LIMIT 1") != "enabled") {
        log_write("error", "inc_services_usage", "Unable to email customer usage alerts, due to EMAIL_ENABLE being disabled");
        return -1;
    }
    // fetch email address to send as.
    $email_sender = sql_get_singlevalue("SELECT value FROM config WHERE name='COMPANY_NAME'") . " <" . sql_get_singlevalue("SELECT value FROM config WHERE name='COMPANY_CONTACT_EMAIL'") . ">";
    /*
    	Run through all the active services
    */
    $sql_custserv_obj = new sql_query();
    $sql_custserv_obj->string = "SELECT id, customerid, serviceid, description, date_period_first, date_period_next, date_period_last FROM services_customers WHERE services_customers.active='1'";
    if ($customerid) {
        $sql_custserv_obj->string .= " AND customerid='{$customerid}'";
    }
    $sql_custserv_obj->execute();
    if ($sql_custserv_obj->num_rows()) {
        $sql_custserv_obj->fetch_array();
        foreach ($sql_custserv_obj->data as $customer_data) {
            /*
            	Process each service that the customer has, provided that it is a usage service. Any non-usage services we
            	can simply skip.
            */
            // fetch service details
            $obj_service = new service_bundle();
            $obj_service->option_type = "customer";
            $obj_service->option_type_id = $customer_data["id"];
            if (!$obj_service->verify_id_options()) {
                log_write("error", "customers_services", "Unable to verify service ID of " . $customer_data["id"] . " as being valid.");
                return 0;
            }
            $obj_service->load_data();
            $obj_service->load_data_options();
            // fetch customer details
            $sql_customer_obj = sql_get_singlerow("SELECT name_customer FROM customers WHERE id='" . $customer_data["customerid"] . "' LIMIT 1");
            $arr_sql_contact = sql_get_singlerow("SELECT id, contact FROM customer_contacts WHERE customer_id = '" . $customer_data["customerid"] . "' AND role = 'accounts' LIMIT 1");
            $arr_sql_contact_details = sql_get_singlerow("SELECT detail AS contact_email FROM customer_contact_records WHERE contact_id = '" . $arr_sql_contact["id"] . "' AND type = 'email' LIMIT 1");
            // place the contact details into the customer details array.
            $sql_customer_obj["name_contact"] = $arr_sql_contact["contact"];
            $sql_customer_obj["contact_email"] = $arr_sql_contact_details["contact_email"];
            // check the service type
            $service_type = sql_get_singlevalue("SELECT name as value FROM service_types WHERE id='" . $obj_service->data["typeid"] . "'");
            // only process data_traffic, time or generic_usage services
            //
            // (call services have usage, but we don't currently alert for those)
            //
            if ($service_type == "generic_with_usage" || $service_type == "time" || $service_type == "data_traffic") {
                log_debug("inc_services_usage", "Processing service " . $customer_data["id"] . " for customer " . $customer_data["customerid"] . "");
                /*
                	Fetch the customer's currently active period.
                */
                $sql_periods_obj = new sql_query();
                $sql_periods_obj->string = "SELECT " . "id, " . "date_start, " . "date_end, " . "usage_summary, " . "usage_alerted " . "FROM services_customers_periods " . "WHERE " . "id_service_customer='" . $customer_data["id"] . "' " . "AND invoiceid_usage = '0' " . "AND date_end >= '" . date("Y-m-d") . "' LIMIT 1";
                $sql_periods_obj->execute();
                if ($sql_periods_obj->num_rows()) {
                    $sql_periods_obj->fetch_array();
                    $period_data = $sql_periods_obj->data[0];
                    // fetch billing mode
                    $billing_mode = sql_get_singlevalue("SELECT name as value FROM billing_modes WHERE id='" . $obj_service->data["billing_mode"] . "'");
                    // fetch unit naming
                    if ($service_type == "generic_with_usage") {
                        $unitname = $obj_service->data["units"];
                    } else {
                        $unitname = sql_get_singlevalue("SELECT name as value FROM service_units WHERE id='" . $obj_service->data["units"] . "'");
                    }
                    /*
                    	Calculate number of included units
                    
                    	TODO: This code is a replicant of the source in include/services/inc_service_invoicegen.php used for calculating
                    	partial	periods and should really be functionalised as part of service usage continual improvements.
                    
                    	Part of the issue is lack of service period OO handling functions - migrating the code to such a setup will make
                    	such ratio calculation functions far, far easier.
                    */
                    // default ratio
                    $ratio = 1;
                    // calculate usage abnormal period
                    if ($obj_service->data["billing_mode_string"] == "monthend" || $obj_service->data["billing_mode_string"] == "monthadvance" || $obj_service->data["billing_mode_string"] == "monthtelco") {
                        log_debug("inc_services_usage", "Usage period service bills by month date");
                        if (time_calculate_daynum($period_data["date_start"]) != "01") {
                            // very first billing month
                            log_write("debug", "inc_services_usage", "First billing month for this usage period, adjusting pricing to suit days.");
                            if ($GLOBALS["config"]["SERVICE_PARTPERIOD_MODE"] == "seporate") {
                                log_write("debug", "inc_services_usage", "Adjusting for partial month period (SERVICE_PARTPERIOD_MODE == seporate)");
                                // work out the total number of days
                                $short_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_data["date_start"]));
                                $short_month_days_short = $short_month_days_total - time_calculate_daynum($period_data["date_start"]);
                                log_write("debug", "inc_services_usage", "Short initial billing period of {$short_month_days_short} days");
                                // calculate ratio
                                $ratio = $short_month_days_short / $short_month_days_total;
                                log_write("debug", "inc_services_usage", "Calculated service bill ratio of {$ratio} to handle short period.");
                            } else {
                                log_write("debug", "inc_services_usage", "Adjusting for extended month period (SERVICE_PARTPERIOD_MODE == merge");
                                // work out the number of days extra
                                $extra_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_data["date_start"]));
                                $extra_month_days_extra = $extra_month_days_total - time_calculate_daynum($period_data["date_start"]);
                                log_debug("inc_services_usage", "{$extra_month_days_extra} additional days ontop of started billing period");
                                // calculate ratio
                                $ratio = ($extra_month_days_extra + $extra_month_days_total) / $extra_month_days_total;
                                log_write("debug", "inc_services_usage", "Calculated service bill ratio of {$ratio} to handle extended period.");
                            }
                        }
                    }
                    // end of calculate usage abnormal period
                    // calculate a final period
                    if ($period_data["date_period_last"] != "0000-00-00") {
                        log_write("debug", "inc_services_usage", "Service has a final period date set (" . $customer_data["date_period_last"] . ")");
                        if ($customer_data["date_period_last"] == $period_data["date_end"] || time_date_to_timestamp($customer_data["date_period_last"]) < time_date_to_timestamp($period_data["date_end"])) {
                            log_write("debug", "inc_services_usage", "Service is a final period, checking for time adjustment (if any)");
                            // fetch the regular end date
                            $orig_dates = service_period_dates_generate($period_data["date_start"], $obj_service->data["billing_cycle_string"], $obj_service->data["billing_mode_string"]);
                            if ($orig_dates["end"] != $period_data["date_end"]) {
                                // work out the total number of days
                                $time = NULL;
                                $time["start"] = time_date_to_timestamp($period_data["date_start"]);
                                $time["end_orig"] = time_date_to_timestamp($orig_dates["end"]);
                                $time["end_new"] = time_date_to_timestamp($period_data["date_end"]);
                                $time["orig_days"] = sprintf("%d", ($time["end_orig"] - $time["start"]) / 86400);
                                $time["new_days"] = sprintf("%d", ($time["end_new"] - $time["start"]) / 86400);
                                log_write("debug", "inc_services_usage", "Short initial billing period of " . $time["new_days"] . " days rather than expected " . $time["orig_days"] . "");
                                // calculate ratio
                                $ratio = $time["new_days"] / $time["orig_days"];
                                log_write("debug", "inc_services_usage", "Calculated service bill ratio of {$ratio} to handle short period.");
                                unset($time);
                            } else {
                                log_write("debug", "inc_services_usage", "Final service period is regular size, no adjustment required.");
                            }
                        }
                    }
                    if ($billing_mode == "monthend" || $billing_mode == "monthadvance") {
                        log_debug("inc_services_usage", "Service is billed by calender month");
                        /*
                        	Handle monthly billing
                        
                        	Normally, monthly billing is easy, however the very first billing period is special, as it may span a more than 1 month.
                        
                        	Eg: if a service is started on 2008-01-09, the end of the billing period will be 2008-02-29, which is 1 month + 21 day.
                        
                        	To handle this, we increase the number of included units by the following method:
                        
                        		( standard_cost / normal_month_num_days ) * num_days_in_partial_month == extra_amount
                        
                        		total_amount = (extra_amount + normal_amount)
                        
                        	Note: This code is based off the section found for inc_services_usage.php. Could be worth creating a function?
                        */
                        // check if the period is the very first period - the start and end dates will be in different months.
                        if (time_calculate_monthnum($period_data["date_start"]) != time_calculate_monthnum($period_data["date_end"])) {
                            // very first billing month
                            log_debug("inc_services_usage", "Very first billing month - adjusting included units to suit the extra time included.");
                            // work out the number of days extra
                            $extra_month_days_total = time_calculate_daynum(time_calculate_monthdate_last($period_data["date_start"]));
                            $extra_month_days_extra = $extra_month_days_total - time_calculate_daynum($period_data["date_start"]);
                            log_debug("inc_services_usage", "{$extra_month_days_extra} additional days ontop of started billing period");
                            // calculate number of included units - round up to nearest full unit
                            $ratio = ($extra_month_days_total + $extra_month_days_extra) / $extra_month_days_total;
                        }
                    }
                    log_write("debug", "inc_services_usage", "Usage period ratio calculated as {$ratio}");
                    /*
                    	^ End of Ratio Calculation - object orientation required around this
                    */
                    /*
                    	Process usage for each service type - there are differences between particular platforms.
                    */
                    switch ($service_type) {
                        case "generic_with_usage":
                        case "time":
                            /*
                            	Fetch the amount of usage
                            */
                            $usage_obj = new service_usage_generic();
                            $usage_obj->id_service_customer = $customer_data["id"];
                            $usage_obj->date_start = $period_data["date_start"];
                            $usage_obj->date_end = $period_data["date_end"];
                            if ($usage_obj->load_data_service()) {
                                $usage_obj->fetch_usagedata();
                                if (isset($usage_obj->data["total_byunits"])) {
                                    $usage = $usage_obj->data["total_byunits"];
                                } else {
                                    $usage = $usage_obj->data["total"];
                                }
                            }
                            unset($usage_obj);
                            /*
                            	Apply Ratio
                            */
                            if ($ratio != "1") {
                                $obj_service->data["included_units"] = sprintf("%d", $obj_service->data["included_units"] * $ratio);
                            }
                            /*
                            	Run usage notification logic
                            */
                            if ($GLOBALS["config"]["SERVICES_USAGEALERTS_ENABLE"]) {
                                $message = "";
                                if ($usage > $obj_service->data["included_units"]) {
                                    // usage is over 100% - check if we should report this
                                    log_debug("inc_service_usage", "Usage is over 100%");
                                    if ($obj_service->data["alert_extraunits"]) {
                                        // check at what usage amount we last reported, and if
                                        // we have used alert_extraunits more usage since then, send
                                        // an alert to the customer.
                                        if ($usage - $period_data["usage_alerted"] >= $obj_service->data["alert_extraunits"]) {
                                            log_write("notification", "inc_service_usage", "Sending excess usage notification (over 100%)");
                                            /*
                                            												Send excess usage notification (over 100%)
                                            	Message Example:
                                            												This email has been sent to advise you that you have gone over the
                                            												included usage on your plan.
                                            	You have now used 70 excess ZZ on your Example Service plan.
                                            	Used 120 ZZ out of 50 ZZ included in plan
                                            												Excess usage of 70 ZZ charged at $5.00 per ZZ (exc taxes)
                                            	Your current billing period ends on YYYY-MM-DD.
                                            */
                                            // there is excess usage
                                            $usage_excess = $usage - $obj_service->data["included_units"];
                                            // prepare message
                                            $message .= "This email has been sent to advise you that you have gone over the included usage on your plan\n";
                                            $message .= "\n";
                                            $message .= "You have now used {$usage_excess} excess {$unitname} on your " . $obj_service->data["name_service"] . " plan.\n";
                                            $message .= "\n";
                                            $message .= "Used {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan.\n";
                                            $message .= "Excess usage of {$usage_excess} {$unitname} charged at " . $obj_service->data["price_extraunits"] . " per {$unitname} (exc taxes).\n";
                                            $message .= "\n";
                                            $message .= "Your current billing period ends on " . $period_data["date_end"] . "\n";
                                            $message .= "\n";
                                            // send email
                                            if ($sql_customer_obj["contact_email"]) {
                                                $headers = "From: {$email_sender}\r\n";
                                                mail($sql_customer_obj["name_contact"] . "<" . $sql_customer_obj["contact_email"] . ">", "Excess usage notification", $message, $headers);
                                            } else {
                                                log_write("error", "inc_service_usage", "Customer " . $sql_customer_obj["name_customer"] . " does not have an email address, unable to send usage notifications.");
                                            }
                                            // update alerted amount
                                            $sql_obj = new sql_query();
                                            $sql_obj->string = "UPDATE services_customers_periods SET usage_alerted='{$usage}' WHERE id='" . $period_data["id"] . "' LIMIT 1";
                                            $sql_obj->execute();
                                        }
                                    }
                                } else {
                                    // calculate 80% of the included usage
                                    $included_usage_80pc = $obj_service->data["included_units"] * 0.8;
                                    if ($usage == $obj_service->data["included_units"]) {
                                        log_debug("inc_service_usage", "Usage is at 100%");
                                        // usage is at 100%
                                        //
                                        // make sure that:
                                        // 1. 100% usage alerting is enabled
                                        // 2. that we have not already sent this alert (by checking period_data["usage_summary"])
                                        //
                                        if ($obj_service->data["alert_100pc"] && $period_data["usage_summary"] < $obj_service->data["included_units"]) {
                                            log_write("notification", "inc_service_usage", "Sending excess usage notification (100% reached)");
                                            /*
                                            	Send 100% usage notification
                                            
                                            	Message Example:
                                            	This email has been sent to advise you that you have used 100% of
                                            	your included usage on your Example Service plan.
                                            
                                            	Used 50 ZZ out of 50 ZZ included in plan
                                            
                                            	Any excess usage will be charged at $5.00 per ZZ (exc taxes)
                                            
                                            	Your current billing period ends on YYYY-MM-DD.
                                            */
                                            // prepare message
                                            $message .= "This email has been sent to advise you that you have used 100% of your included usage on your " . $obj_service->data["name_service"] . " plan.\n";
                                            $message .= "\n";
                                            $message .= "Used {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan.\n";
                                            $message .= "Any excess usage will be charged at " . $obj_service->data["price_extraunits"] . " per {$unitname} (exc taxes).\n";
                                            $message .= "\n";
                                            $message .= "Your current billing period ends on " . $period_data["date_end"] . "\n";
                                            $message .= "\n";
                                            // send email
                                            if ($sql_customer_obj["contact_email"]) {
                                                $headers = "From: {$email_sender}\r\n";
                                                mail($sql_customer_obj["name_contact"] . "<" . $sql_customer_obj["contact_email"] . ">", "100% usage notification", $message, $headers);
                                            } else {
                                                log_write("error", "inc_service_usage", "Customer " . $sql_customer_obj["name_customer"] . " does not have an email address, unable to send usage notifications.");
                                            }
                                            // update alerted amount
                                            $sql_obj = new sql_query();
                                            $sql_obj->string = "UPDATE services_customers_periods SET usage_alerted='{$usage}' WHERE id='" . $period_data["id"] . "' LIMIT 1";
                                            $sql_obj->execute();
                                        }
                                    } elseif ($usage > $included_usage_80pc) {
                                        log_debug("inc_service_usage", "Usage is between 80% & 100%");
                                        // usage is between 80 and 100%
                                        //
                                        // make sure that:
                                        // 1. 80% usage alerting is enabled
                                        // 2. that we have not already sent this alert (by checking period_data["usage_summary"])
                                        //
                                        if ($obj_service->data["alert_80pc"] && $period_data["usage_summary"] < $included_usage_80pc) {
                                            log_write("notification", "inc_service_usage", "Sending excess usage notification (80% - 100%)");
                                            /*
                                            	Send 80% usage notification
                                            
                                            	Message Example:
                                            	This email has been sent to advise you that you have used over 80% of
                                            	your included usage on your Example Service plan.
                                            
                                            	Used 50 ZZ out of 50 ZZ included in plan
                                            
                                            	Any excess usage will be charged at $5.00 per ZZ (exc taxes)
                                            
                                            	Your current billing period ends on YYYY-MM-DD.
                                            */
                                            // prepare message
                                            $message .= "This email has been sent to advise you that you have used over 80% of your included usage on your " . $obj_service->data["name_service"] . " plan.\n";
                                            $message .= "\n";
                                            $message .= "Used {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan.\n";
                                            $message .= "Any excess usage will be charged at " . $obj_service->data["price_extraunits"] . " per {$unitname} (exc taxes).\n";
                                            $message .= "\n";
                                            $message .= "Your current billing period ends on " . $period_data["date_end"] . "\n";
                                            $message .= "\n";
                                            // fetch email
                                            // send email
                                            if ($sql_customer_obj["contact_email"]) {
                                                $headers = "From: {$email_sender}\r\n";
                                                mail($sql_customer_obj["name_contact"] . "<" . $sql_customer_obj["contact_email"] . ">", "80% usage notification", $message, $headers);
                                            } else {
                                                log_write("error", "inc_service_usage", "Customer " . $sql_customer_obj["name_customer"] . " does not have an email address, unable to send usage notifications.");
                                            }
                                            // update alerted amount
                                            $sql_obj = new sql_query();
                                            $sql_obj->string = "UPDATE services_customers_periods SET usage_alerted='{$usage}' WHERE id='" . $period_data["id"] . "' LIMIT 1";
                                            $sql_obj->execute();
                                        }
                                    }
                                }
                            } else {
                                log_write("notification", "inc_service_usage", "Not sending usage notification/reminder due to SERVICES_USAGEALERTS_ENABLE being disabled");
                            }
                            /*
                            	Update Usage Summary - this used for various user interfaces and is the *total* transfer usage report.
                            */
                            $sql_obj = new sql_query();
                            $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='{$usage}' WHERE id='" . $period_data["id"] . "' LIMIT 1";
                            $sql_obj->execute();
                            break;
                        case "data_traffic":
                            /*
                            	DATA_TRAFFIC
                            
                            	Data traffic services are more complex for usage checks and notifications than other services, due to the need to 
                            	fetch usage amounts for each cap type and notify as appropiate.
                            
                            	Some data services may also be uncapped/unlimited, in which case we want to record their current usage amount but
                            	won't need to ever send usage notifications.
                            */
                            /*
                            	Fetch Traffic Caps & Details
                            
                            	Returns all the traffic cap types including overrides.
                            
                            	id_type,
                            	id_cap,
                            	type_name,
                            	type_label,
                            	cap_mode,
                            	cap_units_included,
                            	cap_units_price
                            */
                            $traffic_types_obj = new traffic_caps();
                            $traffic_types_obj->id_service = $customer_data["serviceid"];
                            $traffic_types_obj->id_service_customer = $customer_data["id"];
                            $traffic_types_obj->load_data_traffic_caps();
                            $traffic_types_obj->load_data_override_caps();
                            /*
                            	Fetch the amount of usage
                            */
                            $usage_obj = new service_usage_traffic();
                            $usage_obj->id_service_customer = $customer_data["id"];
                            $usage_obj->date_start = $period_data["date_start"];
                            $usage_obj->date_end = $period_data["date_end"];
                            if ($usage_obj->load_data_service()) {
                                $usage_obj->fetch_usage_traffic();
                            }
                            /*
                            	Update service usage database record for each cap
                            
                            	Create a new usage alert summary record - this record defines the usage as at a certain date
                            	and tracks whether usage alerts were sent or not.
                            */
                            $usage_alert_id = array();
                            // holds IDs of inserted rows
                            foreach ($traffic_types_obj->data as $traffic_cap) {
                                log_write("debug", "inc_service_usage", "Service " . $customer_data["id"] . " data usage for traffic type " . $traffic_type["type_name"] . " is " . $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] . " units");
                                // create new record
                                $sql_obj = new sql_query();
                                $sql_obj->string = "INSERT INTO service_usage_alerts\n\t\t\t\t\t\t\t\t\t\t\t\t(id_service_customer,\n\t\t\t\t\t\t\t\t\t\t\t\t id_service_period,\n\t\t\t\t\t\t\t\t\t\t\t\t id_type,\n\t\t\t\t\t\t\t\t\t\t\t\t date_update,\n\t\t\t\t\t\t\t\t\t\t\t\t usage_current)\n\t\t\t\t\t\t\t\t\t\t\t\tVALUES\n\t\t\t\t\t\t\t\t\t\t\t\t('" . $customer_data["id"] . "',\n\t\t\t\t\t\t\t\t\t\t\t\t '" . $period_data["id"] . "',\n\t\t\t\t\t\t\t\t\t\t\t\t '" . $traffic_cap["id_type"] . "',\n\t\t\t\t\t\t\t\t\t\t\t\t '" . date("Y-m-d") . "',\n\t\t\t\t\t\t\t\t\t\t\t\t '" . $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] . "')";
                                $sql_obj->execute();
                                // record type label ID
                                $usage_alert_id[$traffic_cap["type_label"]] = $sql_obj->fetch_insert_id();
                            }
                            // Update Usage Summary - this used for various user interfaces and is the *total* transfer usage report.
                            log_write("notification", "inc_service_usage", "Customer " . $sql_customer_obj["name_customer"] . " has used a total of " . $usage_obj->data["total_byunits"]["total"] . " {$unitname} traffic on service \"" . $obj_service->data["name_service"] . "\"");
                            $sql_obj = new sql_query();
                            $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='" . $usage_obj->data["total_byunits"]["total"] . "' WHERE id='" . $period_data["id"] . "' LIMIT 1";
                            $sql_obj->execute();
                            /*
                            	Run usage notification logic
                            */
                            if ($GLOBALS["config"]["SERVICES_USAGEALERTS_ENABLE"]) {
                                // fetch usage - in particular, the last usage amount that we alerted for.
                                $usage = $usage_obj->data["total_byunits"][$traffic_cap["type_label"]];
                                $usage_alerted = 0;
                                $sql_obj->string = "SELECT date_sent, usage_alerted FROM service_usage_alerts WHERE id_service_customer='" . $customer_data["id"] . "' AND id_service_period='" . $period_data["id"] . "' AND id_type='" . $traffic_cap["id_type"] . "' AND id!='" . $usage_alert_id[$traffic_cap["type_label"]] . "' ORDER BY date_sent DESC, id DESC LIMIT 1";
                                $sql_obj->execute();
                                if ($sql_obj->num_rows()) {
                                    $sql_obj->fetch_array();
                                    if ($sql_obj->data[0]["date_sent"] != "0000-00-00") {
                                        $usage_alerted = $sql_obj->data[0]["usage_alerted"];
                                    }
                                }
                                // used to flag the usage caps that need alerting
                                $alert_80 = array();
                                $alert_100 = array();
                                $alert_extra = array();
                                $alert_none = array();
                                // run through current caps, flag all which need notifications.
                                $j = 0;
                                foreach ($traffic_types_obj->data as $traffic_cap) {
                                    // we don't care about unlimited traffic
                                    if ($traffic_cap["cap_mode"] != "capped") {
                                        // skip
                                        log_write("debug", "inc_service_usage", "Skipping traffic cap due to mode of " . $traffic_cap["cap_mode"] . "");
                                        $alert_none[] = $traffic_cap["type_label"];
                                        continue;
                                    }
                                    // determine caps
                                    if ($ratio != "1") {
                                        // recalculate for short or long months
                                        $cap_units_included = sprintf("%d", $traffic_cap["cap_units_included"] * $ratio);
                                        // save for rest of session
                                        $traffic_types_obj->data[$j]["cap_units_included"] = $cap_units_included;
                                    } else {
                                        // no changes
                                        $cap_units_included = $traffic_cap["cap_units_included"];
                                    }
                                    // determine threshholds
                                    $cap_100 = $cap_units_included;
                                    $cap_80 = $cap_units_included * 0.8;
                                    if ($usage >= $cap_100) {
                                        // usage is at or over 100%
                                        if ($usage < $cap_100 + $obj_service->data["alert_extraunits"]) {
                                            // just over 100%, but less than the excess alert count - consider as 100%
                                            if ($usage_alerted < $cap_100) {
                                                $alert_100[] = $traffic_cap["type_label"];
                                            } else {
                                                $alert_none[] = $traffic_cap["type_label"];
                                            }
                                        } else {
                                            // well over 100%
                                            if ($obj_service->data["alert_extraunits"]) {
                                                // check at what usage amount we last reported, and if
                                                // we have used alert_extraunits+ more usage since then, send
                                                // an alert to the customer.
                                                if ($usage - $usage_alerted >= $obj_service->data["alert_extraunits"]) {
                                                    // excess usage
                                                    $alert_extra[] = $traffic_cap["type_label"];
                                                } else {
                                                    $alert_none[] = $traffic_cap["type_label"];
                                                }
                                            } else {
                                                // no extra unit alerts configured, so we should not alert to excess
                                                // usage.
                                                $alert_none[] = $traffic_cap["type_label"];
                                            }
                                        }
                                    } elseif ($usage >= $cap_80 && $usage < $cap_100) {
                                        // usage between 80% and 100%
                                        if ($obj_service->data["alert_80pc"] && $usage_alerted < $cap_80) {
                                            // we haven't alerted for this yet, so flag it
                                            $alert_80[] = $traffic_cap["type_label"];
                                        } else {
                                            $alert_none[] = $traffic_cap["type_label"];
                                        }
                                    } else {
                                        // usage is below 80% mark
                                        $alert_none[] = $traffic_cap["type_label"];
                                    }
                                    $j++;
                                }
                                // end of traffic loops
                                log_write("debug", "inc_service_usage", "Following data caps are at 80% alert: " . format_arraytocommastring($alert_80) . "");
                                log_write("debug", "inc_service_usage", "Following data caps are at 100% alert: " . format_arraytocommastring($alert_100) . "");
                                log_write("debug", "inc_service_usage", "Following data caps are at 100% + extra blocks alert: " . format_arraytocommastring($alert_extra) . "");
                                log_write("debug", "inc_service_usage", "Following data caps do not require alerting: " . format_arraytocommastring($alert_none) . "");
                                /*
                                	Process Usage Notifications
                                
                                	Here we need to loop through all caps flagged for notifications and write a message to the customer
                                	for all overage caps
                                */
                                if (!empty($alert_80) || !empty($alert_100) || !empty($alert_extra)) {
                                    log_write("debug", "inc_service_usage", "Alerting for service, preparing email message.");
                                    /*
                                    	Now we run through all the alert flagged data caps and use it to assemble a usage notification warning email.
                                    
                                    
                                    	Example Message
                                    	---------------
                                    
                                    	DATA USAGE ADVISORY
                                    
                                    	This email has been sent to advise you about your data service usage as of 18-05-2011.
                                    
                                    	Service "My Example Internet Service"
                                    	
                                    
                                    	NATIONAL
                                    
                                    	You have used 150% of your National data cap.
                                    
                                    	Used 15GB out of 10GB included in plan.
                                    	Excess usage of 5GB charged at $5.00 per GB (exc taxes)
                                    
                                    
                                    	INTERNATIONAL
                                    
                                    	You have used 84% of your International data cap.
                                    
                                    	Used 84GB out of 100GB included in plan.
                                    	Any future excess usage will be charged at $8.00 per GB (exc taxes)
                                    
                                    
                                    	BILLING PERIOD
                                    
                                    	Your current billing period ends on YYYY-MM-DD.
                                    
                                    	{optional} Note:
                                    	This billing period is longer/shorter than your regular billing period, this may mean
                                    	your data cap allocations appear different to normal to refect the longer/shorter period.
                                    
                                    	This typically occurs when you first signup to a service, upgrade a service or cancel a service.
                                    */
                                    $message = "\n";
                                    $message .= "DATA USAGE ADVISORY\n";
                                    $message .= "\n";
                                    $message .= "This email has been sent to advise you about your data service usage as of " . time_format_humandate() . "\n";
                                    $message .= "\n";
                                    $message .= "Service \"" . $obj_service->data["name_service"] . "\"\n";
                                    $message .= "\n";
                                    $message .= "\n";
                                    $message .= "\n";
                                    foreach ($traffic_types_obj->data as $traffic_cap) {
                                        log_write("debug", "inc_service_usage", "Preparing usage warning email messages for: \"" . $traffic_cap["type_label"] . "\"");
                                        // determine percentage used
                                        $percentage = sprintf("%d", $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] / $traffic_cap["cap_units_included"] * 100);
                                        // if there is only one cap, adjust label
                                        if ($traffic_types_obj->data_num_rows == 1) {
                                            $traffic_cap["type_name"] = "All Traffic";
                                        }
                                        // 80%-100%
                                        if (in_array($traffic_cap["type_label"], $alert_80)) {
                                            $message .= strtoupper($traffic_cap["type_name"]) . "\n";
                                            $message .= "\n";
                                            $message .= "You have used {$percentage}% of your " . $traffic_cap["type_name"] . " data cap\n";
                                            $message .= "\n";
                                            $message .= "Used " . $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] . " {$unitname} out of " . $traffic_cap["cap_units_included"] . " {$unitname} included in plan.\n";
                                            $message .= "Any future excess usage will be charged at " . format_money($traffic_cap["cap_units_price"]) . " per {$unitname} (exc taxes)\n";
                                            $message .= "\n\n";
                                        }
                                        // 100%
                                        if (in_array($traffic_cap["type_label"], $alert_100)) {
                                            $message .= strtoupper($traffic_cap["type_name"]) . "\n";
                                            $message .= "\n";
                                            $message .= "You have used {$percentage}% of your " . $traffic_cap["type_name"] . " data cap\n";
                                            $message .= "\n";
                                            $message .= "Used " . $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] . " {$unitname} out of " . $traffic_cap["cap_units_included"] . " {$unitname} included in plan.\n";
                                            $message .= "Any future excess usage will be charged at " . format_money($traffic_cap["cap_units_price"]) . " per {$unitname} (exc taxes)\n";
                                            $message .= "\n\n";
                                        }
                                        // 100% ++ excess
                                        if (in_array($traffic_cap["type_label"], $alert_extra)) {
                                            $usage_excess = $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] - $traffic_cap["cap_units_included"];
                                            $message .= strtoupper($traffic_cap["type_name"]) . "\n";
                                            $message .= "\n";
                                            $message .= "You have used {$percentage}% of your " . $traffic_cap["type_name"] . " data cap\n";
                                            $message .= "\n";
                                            $message .= "Used " . $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] . " out of " . $traffic_cap["cap_units_included"] . " {$unitname} included in plan.\n";
                                            $message .= "Excess usage of " . $usage_excess . " {$unitname} charged at " . format_money($traffic_cap["cap_units_price"]) . " per {$unitname} (exc taxes)\n";
                                            $message .= "\n\n";
                                        }
                                    }
                                    /*
                                    	Footer
                                    */
                                    $message .= "BILLING PERIOD\n";
                                    $message .= "\n";
                                    $message .= "Your current billing period ends on " . time_format_humandate($period_data["date_end"]) . "\n";
                                    $message .= "\n";
                                    /*
                                    	Tell user about long/short periods to avoid inevidable accounts enquires relating to their usage
                                    */
                                    if ($ratio < 1) {
                                        // shorter period
                                        $message .= "\n";
                                        $message .= "Important Note: This billing period is shorter than your regular billing period, this may mean your data cap allocations appear smaller than normal to refect the shorter period. This typically occurs when you first signup to a service, upgrade a service or cancel a service.\n";
                                    } elseif ($ratio > 1) {
                                        // longer period
                                        $message .= "\n";
                                        $message .= "Important Note: This billing period is longer than your regular billing period, this may mean your data cap allocations appear larger than normal to refect the longer period. This typically occurs when you first signup to a service, upgrade a service or cancel a service.\n";
                                    }
                                    /*
                                    	Issue Email
                                    
                                    	TODO:	Future enhancements will include the capability to select a usage contact email address first, before
                                    		falling back to the regular accounts email address. This will make it easier for companies to send bills
                                    		to AR but usage alerts to staff.
                                    */
                                    if ($sql_customer_obj["contact_email"]) {
                                        $headers = "From: {$email_sender}\r\n";
                                        mail($sql_customer_obj["name_contact"] . "<" . $sql_customer_obj["contact_email"] . ">", "Service usage notification", $message, $headers);
                                        log_write("notification", "inc_service_usage", "Issuing usage notification email to " . $sql_customer_obj["name_contact"] . " at " . $sql_customer_obj["contact_email"] . "");
                                    } else {
                                        log_write("error", "inc_service_usage", "Customer " . $sql_customer_obj["name_customer"] . " does not have an email address, unable to send usage notifications.");
                                    }
                                    /*	
                                    	Update alerted amount tracker
                                    */
                                    $sql_obj = new sql_query();
                                    $sql_obj->string = "UPDATE service_usage_alerts SET usage_alerted='" . $usage_obj->data["total_byunits"][$traffic_cap["type_label"]] . "', date_sent='" . date("Y-m-d") . "' WHERE id='" . $usage_alert_id[$traffic_cap["type_label"]] . "' LIMIT 1";
                                    $sql_obj->execute();
                                }
                                // end if usage notifications to process
                            }
                            // end if alerts enabled
                            unset($traffic_types_obj);
                            unset($usage_obj);
                            break;
                    }
                    // end of switch service type
                }
                // end if usage periods exist
            }
            // end if a usage service
        }
        // end of loop through customer services
    }
    // end if customer(s) services exist
}