} } /* Test CDR Database */ if ($data["SERVICE_CDR_DB_TYPE"] == "mysql_cdr_daily") { $obj_sql = new sql_query(); if (!$obj_sql->session_init("mysql", $data["SERVICE_CDR_DB_HOST"], $data["SERVICE_CDR_DB_NAME"], $data["SERVICE_CDR_DB_USERNAME"], $data["SERVICE_CDR_DB_PASSWORD"])) { log_write("error", "sql_query", "Unable to connect to CDR service usage database!"); error_flag_field("SERVICE_CDR_DB_HOST"); error_flag_field("SERVICE_CDR_DB_NAME"); error_flag_field("SERVICE_CDR_DB_USERNAME"); error_flag_field("SERVICE_CDR_DB_PASSWORD"); } else { log_write("notification", "sql_query", "Tested successful connection to CDR usage database"); $obj_sql->session_terminate(); } } /* Process Errors */ if (error_check()) { $_SESSION["error"]["form"]["config_services"] = "failed"; header("Location: ../index.php?page=admin/config_services.php"); exit(0); } else { $_SESSION["error"] = array(); /* Start Transaction */ $sql_obj = new sql_query();
function fetch_usage_calls() { log_write("debug", "service_usage_cdr", "Executing fetch_usage_calls()"); /* Load Call Pricing (including overrides for this customer) */ $obj_cdr_rate_table = new cdr_rate_table_rates_override(); $obj_cdr_rate_table->option_type = "customer"; $obj_cdr_rate_table->option_type_id = $this->id_service_customer; $obj_cdr_rate_table->verify_id_override(); $obj_cdr_rate_table->load_data(); $obj_cdr_rate_table->load_data_rate_all(); $obj_cdr_rate_table->load_data_rate_all_override(); /* We need a list of the DDIs for this customer. */ if (!$this->data_ddi) { $this->load_data_ddi(); } /* Query Call Records */ if ($GLOBALS["config"]["SERVICE_CDR_MODE"] == "internal") { log_write("debug", "service_usage_cdr", "Fetching call records from internal ABS database.."); /* Whilst for large datasets, Amberdms recommends using an external CDR database, it may be desirable to use the internal ABS call data database for record billing, such as when importing from another system via the API. when using the internal database, the service_usage_records table is used: date Date of Call price Price of call usage1 Source DDI usage2 Destination DDI usage3 Billable call seconds Note that we don't need to loop through the DDIs here, since anyone using the internal database has already matched the DDIs to the id_service_customer via the API usage upload. The way the internal database is used will vary depending on the information provided - if a price is already set, this price will be used for the total call cost. If no price is set, the costs will be looked up in the rate table. This behaviour allows charged call rates to be imported from another platform and not having to be re-calculated against ABS's own rate table. */ // calculate end date to be the first date of the next period - failure to do so would mean we would // miss the last day of the billing period in the query; $date_start = $this->date_start; $tmp_date = explode("-", $this->date_end); $date_end = date("Y-m-d", mktime(0, 0, 0, $tmp_date[1], $tmp_date[2] + 1, $tmp_date[0])); /* Fetch Data Just a simple query here, however if this is a TOLLFREE service, then we need to reverse the query to charge for inbound calls rather than outbound. */ log_write("debug", "service_usage_cdr", "Fetching usage records FOR {$ddi} FROM {$date_start} TO {$date_end}"); $obj_cdr_sql = new sql_query(); if ($this->obj_service->data["typeid_string"] == "phone_tollfree") { log_write("debug", "service_usage_cdr", "Billing for tollfree service on {$ddi}"); // NOTE! for toll-free services, we reverse src and dst for reverse billing calculations $obj_cdr_sql->string = "SELECT id, date, price, usage1 as dst, usage2 as src, usage3 as billsec, billgroup FROM service_usage_records WHERE id_service_customer='" . $this->id_service_customer . "' AND date >= '{$date_start}' AND date < '{$date_end}'"; $obj_cdr_sql->execute(); } else { $obj_cdr_sql->string = "SELECT id, date, price, usage1 as src, usage2 as dst, usage3 as billsec, billgroup FROM service_usage_records WHERE id_service_customer='" . $this->id_service_customer . "' AND date >= '{$date_start}' AND date < '{$date_end}'"; $obj_cdr_sql->execute(); } /* Calculate costs of calls */ if ($obj_cdr_sql->num_rows()) { $obj_cdr_sql->fetch_array(); foreach ($obj_cdr_sql->data as $data_cdr) { // determine price if ($data_cdr["price"] != "0.00") { // a price has already been set - make use of that $charges = array(); $charges["price"] = $data_cdr["price"]; $charges["billgroup"] = $data_cdr["billgroup"]; } else { $charges = $obj_cdr_rate_table->calculate_charges($data_cdr["billsec"], $data_cdr["src"], $data_cdr["dst"], $this->data_local[$data_cdr["src"]], $this->data_ddi); // update the charges in the records $sql_obj = new sql_query(); $sql_obj->string = "UPDATE service_usage_records SET price='" . $charges["price"] . "', billgroup='" . $charges['billgroup'] . "' WHERE id='" . $data_cdr["id"] . "'"; $sql_obj->execute(); } // add to structure - we use the SRC as the DDI $this->data[$data_cdr["src"]][$charges["billgroup"]]["charges"] += $charges["price"]; // TODO: this won't catch issues where the DDIs configured don't match the SRC DDI (although it should always!) } } } else { /* Connect to External SQL database */ // fetch all calls for that DDI from the DB for the selected period $obj_cdr_db_sql = new sql_query(); if (!$obj_cdr_db_sql->session_init("mysql", $GLOBALS["config"]["SERVICE_CDR_DB_HOST"], $GLOBALS["config"]["SERVICE_CDR_DB_NAME"], $GLOBALS["config"]["SERVICE_CDR_DB_USERNAME"], $GLOBALS["config"]["SERVICE_CDR_DB_PASSWORD"])) { return 0; } /* We fetch the call records by looping through all the DDIs for this customer, fetching all the records for those DDIs and then calculating the cost for each call */ foreach ($this->data_ddi as $ddi) { // calculate end date to be the first date of the next period - failure to do so would mean we would // miss the last day of the billing period in the query; $date_start = $this->date_start; $tmp_date = explode("-", $this->date_end); $date_end = date("Y-m-d", mktime(0, 0, 0, $tmp_date[1], $tmp_date[2] + 1, $tmp_date[0])); /* Fetch Data Just a simple query here, however if this is a TOLLFREE service, then we need to reverse the query to charge for inbound calls rather than outbound. */ log_write("debug", "service_usage_cdr", "Fetching usage records FOR {$ddi} FROM {$date_start} TO {$date_end}"); if ($this->obj_service->data["typeid_string"] == "phone_tollfree") { log_write("debug", "service_usage_cdr", "Billing for tollfree service on {$ddi}"); // NOTE! for toll-free services, we reverse src and dst for reverse billing calculations $obj_cdr_db_sql->string = "SELECT calldate, billsec, dst as src, src as dst FROM cdr WHERE disposition='ANSWERED' AND dst='{$ddi}' AND calldate >= '{$date_start}' AND calldate < '{$date_end}'"; $obj_cdr_db_sql->execute(); } else { $obj_cdr_db_sql->string = "SELECT calldate, billsec, src, dst FROM cdr WHERE disposition='ANSWERED' AND src='{$ddi}' AND calldate >= '{$date_start}' AND calldate < '{$date_end}'"; $obj_cdr_db_sql->execute(); } /* Calculate costs of calls */ if (!isset($this->data[$ddi][$charges["billgroup"]]["charges"])) { $this->data[$ddi][$charges["billgroup"]]["charges"] = 0; } if ($obj_cdr_db_sql->num_rows()) { $obj_cdr_db_sql->fetch_array(); foreach ($obj_cdr_db_sql->data as $data_cdr) { // determine price $charges = $obj_cdr_rate_table->calculate_charges($data_cdr["billsec"], $data_cdr["src"], $data_cdr["dst"], $this->data_local[$ddi], $this->data_ddi); // create local usage record for record keeping purposes $sql_obj = new sql_query(); $sql_obj->string = "INSERT INTO service_usage_records (id_service_customer, date, price, usage1, usage2, usage3, billgroup) VALUES ('" . $this->id_service_customer . "', '" . $data_cdr["calldate"] . "', '" . $charges["price"] . "', '" . $data_cdr["src"] . "', '" . $data_cdr["dst"] . "', '" . $data_cdr["billsec"] . "', '" . $charges["billgroup"] . "')"; $sql_obj->execute(); // add to structure $this->data[$ddi][$charges["billgroup"]]["charges"] += $charges["price"]; } } } // end of DDI loop /* Disconnect from database */ $obj_cdr_db_sql->session_terminate(); } // end of external data source }
function fetch_usage_traffic() { log_write("debug", "service_usage_traffic", "Executing fetch_usage_traffic()"); /* Fetch data traffic types Note that this doesn't query overrides, since the override options will never impact which traffic types that exist, only how they are billed by include/services/inc_services_invoicegen.php */ $traffic_types = sql_get_singlecol("SELECT traffic_types.type_label as col FROM `traffic_caps` LEFT JOIN traffic_types ON traffic_types.id = traffic_caps.id_traffic_type WHERE traffic_caps.id_service='" . $this->obj_service->id . "'"); /* Fetch raw usage data from DB */ if ($GLOBALS["config"]["SERVICE_TRAFFIC_MODE"] == "internal") { /* Internal Database Use the internal database - this stores the usage information for upload/download mapped against the customer's IP address. TODO: Currently all traffic is just assigned against type any/*, this should be upgraded to properly support the different traffic types. */ log_write("debug", "service_usage_traffic", "Fetching traffic records from internal database"); // fetch upload/download stats $sql_obj = new sql_query(); $sql_obj->string = "SELECT SUM(usage1) as usage1, SUM(usage2) as usage2 FROM service_usage_records WHERE id_service_customer='" . $this->id_service_customer . "' AND date>='" . $this->date_start . "' AND date<='" . $this->date_end . "'"; $sql_obj->execute(); $sql_obj->fetch_array(); $this->data["usage1"] = $sql_obj->data[0]["usage1"]; $this->data["usage2"] = $sql_obj->data[0]["usage2"]; unset($sql_obj); // create a total of both usage columns $sql_obj = new sql_query(); $sql_obj->string = "SELECT '" . $this->data["usage1"] . "' + '" . $this->data["usage2"] . "' as totalusage"; $sql_obj->execute(); $sql_obj->fetch_array(); $this->data["total"]["*"] = $sql_obj->data[0]["totalusage"]; $this->data["total"]["total"] = $sql_obj->data[0]["totalusage"]; unset($sql_obj); // we now have the raw usage log_write("debug", "service_usage_traffic", "Total raw traffic usage for " . $this->date_start . " until " . $this->date_end . " is " . $this->data["total"]["total"] . " bytes"); } else { /* Connect to External SQL database External DBs are common with larger teleco providers since it allows easier storage, splicing and archiving of usage information for data traffic services. */ switch ($GLOBALS["config"]["SERVICE_TRAFFIC_DB_TYPE"]) { case "mysql_netflow_daily": /* MODE: mysql_netflow_daily In this mode, there are netflow tables for each day which we need to read through and aggregate data from, typically this is done so that busy ISPs don't end up with massive monthly/yearly tables. eg: traffic_20110420 traffic_20110421 traffic_20110422 TODO: Currently all traffic is just assigned against type any/*, this should be upgraded to properly support the different traffic types. */ log_write("debug", "service_usage_traffic", "Processing external database mysql_netflow_daily"); /* Connect to external database */ $obj_traffic_db_sql = new sql_query(); if (!$obj_traffic_db_sql->session_init("mysql", $GLOBALS["config"]["SERVICE_TRAFFIC_DB_HOST"], $GLOBALS["config"]["SERVICE_TRAFFIC_DB_NAME"], $GLOBALS["config"]["SERVICE_TRAFFIC_DB_USERNAME"], $GLOBALS["config"]["SERVICE_TRAFFIC_DB_PASSWORD"])) { log_write("error", "service_usage_traffic", "Unable to establish a connection to the external traffic DB, unable to run data usage processing."); return 0; } /* Workout the date range, since we need to query a different table for each day TODO: this would be nice as a generic function? */ $tmp_date = $this->date_start; $date_range = array(); $date_range[] = $this->date_start; while ($tmp_date != $this->date_end) { $tmp_date = explode("-", $tmp_date); $tmp_date = date("Y-m-d", mktime(0, 0, 0, $tmp_date[1], $tmp_date[2] + 1, $tmp_date[0])); $date_range[] = $tmp_date; } for ($i = 0; $i < count($date_range); $i++) { // strip "-" charactor $date_range[$i] = str_replace("-", "", $date_range[$i]); } /* We work out the usage by fetching the totals for each IPv4 address belonging to this service and aggregating the total. */ // make sure we have the array of IPv4 addresses if (!$this->data_ipv4) { $this->load_data_ipv4(); } // blank current total $this->data["total"]["total"] = 0; // verify IPv4 address have been configured if (!is_array($this->data_ipv4)) { log_write("warning", "service_usage_traffic", "Note: No IPv4 addresses have been configured for this customer"); return 0; } // run through each IP foreach ($this->data_ipv4 as $ipv4) { /* Fetch Data We run through each IP and for each IP, we fetch the total from all the daily tables. Note that we make the assumption that daily tables might not exist if there's nothing to be processed for that day, so the code is written accordingly. Note that we use the SQL database for *ALL* calculations, this is due to the SQL DB being able to handle 64bit integers, whereas PHP will vary depending on the host platform. */ log_write("debug", "service_usage_traffic", "Fetching usage records FOR address {$ipv4} FOR date " . $this->date_start . " to " . $this->date_end . ""); // run through the dates foreach ($date_range as $date) { // check that the table exists $obj_traffic_db_sql->string = "SHOW TABLES LIKE 'traffic_{$date}'"; $obj_traffic_db_sql->execute(); if ($obj_traffic_db_sql->num_rows()) { // query the current date for the current IP $obj_traffic_db_sql->string = "SELECT SUM(bytes) as total FROM traffic_{$date} WHERE ip_src='{$ipv4}' OR ip_dst='{$ipv4}'"; $obj_traffic_db_sql->execute(); $obj_traffic_db_sql->fetch_array(); if (!empty($obj_traffic_db_sql->data[0]["total"])) { // add to running total $sql_obj = new sql_query(); $sql_obj->string = "SELECT '" . $this->data["total"]["total"] . "' + '" . $obj_traffic_db_sql->data[0]["total"] . "' as totalusage"; $sql_obj->execute(); $sql_obj->fetch_array(); $this->data["total"]["total"] = $sql_obj->data[0]["totalusage"]; } // end if traffic exists } else { log_write("warning", "service_usage_traffic", "SQL database table traffic_{$date} does not exist"); } } // end foreach date log_write("debug", "service_usage_traffic", "Completed usage query for address {$ipv4}."); } // end foreach ipv4 log_write("debug", "service_usage_traffic", "Total usage for all addresses in the date range is " . $this->data["total"]["total"] . " bytes"); /* Disconnect from database */ $obj_traffic_db_sql->session_terminate(); unset($obj_traffic_db_sql); break; case "mysql_traffic_summary": /* MODE: mysql_traffic_summary In this mode, the database contains a single table "traffic_summary" which includes the following key fields: * ip_address IPv4 Address * traffic_datetime Date/Time Field * traffic_type Type of traffic * total Total Bytes transfered Ideally this table should contain one row per IP address, per day, to enable billing to occur. TODO: update to support traffic types */ log_write("debug", "service_usage_traffic", "Processing external database mysql_traffic_summary"); /* Connect to external database */ $obj_traffic_db_sql = new sql_query(); if (!$obj_traffic_db_sql->session_init("mysql", $GLOBALS["config"]["SERVICE_TRAFFIC_DB_HOST"], $GLOBALS["config"]["SERVICE_TRAFFIC_DB_NAME"], $GLOBALS["config"]["SERVICE_TRAFFIC_DB_USERNAME"], $GLOBALS["config"]["SERVICE_TRAFFIC_DB_PASSWORD"])) { log_write("error", "service_usage_traffic", "Unable to establish a connection to the external traffic DB, unable to run data usage processing."); return 0; } /* Loop through each IP and fetch usage for that IP. */ // make sure we have the array of IPv4 addresses if (!$this->data_ipv4) { $this->load_data_ipv4(); } // blank current overall and per-type totals $this->data["total"]["total"] = 0; $this->data["total_byunits"]["total"] = 0; foreach ($traffic_types as $type) { $this->data["total"][$type] = 0; $this->data["total_byunits"][$type] = 0; } // verify IPv4 address have been configured if (!is_array($this->data_ipv4)) { log_write("warning", "service_usage_traffic", "Note: No IPv4 addresses have been configured for this customer"); return 0; } // run through each IP foreach ($this->data_ipv4 as $ipv4) { /* Fetch Data We run through each IP and for each IP, we fetch the total for the date range. Note that we use the SQL database for *ALL* calculations, this is due to the SQL DB being able to handle 64bit integers, whereas PHP will vary depending on the host platform. */ log_write("debug", "service_usage_traffic", "Fetching usage records FOR address {$ipv4} FOR date " . $this->date_start . " to " . $this->date_end . ""); // check that the table exists $obj_traffic_db_sql->string = "SHOW TABLES LIKE 'traffic_summary'"; $obj_traffic_db_sql->execute(); if ($obj_traffic_db_sql->num_rows()) { // query the current date for the current IP $obj_traffic_db_sql->string = "SELECT SUM(total) as total, traffic_type FROM traffic_summary WHERE ip_address='{$ipv4}' AND traffic_datetime >= '" . $this->date_start . "' AND traffic_datetime <= '" . $this->date_end . "' GROUP BY traffic_type"; $obj_traffic_db_sql->execute(); if ($obj_traffic_db_sql->num_rows()) { $obj_traffic_db_sql->fetch_array(); foreach ($obj_traffic_db_sql->data as $data_traffic) { if (in_array($data_traffic["traffic_type"], $traffic_types)) { $type = $data_traffic["traffic_type"]; } else { // unmatched type, assign to any $type = "*"; } // add to running total $sql_obj = new sql_query(); $sql_obj->string = "SELECT '" . $this->data["total"][$type] . "' + '" . $data_traffic["total"] . "' as totalusage"; $sql_obj->execute(); $sql_obj->fetch_array(); $this->data["total"][$type] = $sql_obj->data[0]["totalusage"]; unset($sql_obj); } } // end if traffic exists } else { log_write("error", "service_usage_traffic", "SQL database table traffic_summary does not exist"); } log_write("debug", "service_usage_traffic", "Total usage for address {$ipv4} is " . $this->data["total"] . " bytes"); } // end foreach ipv4 // produce overall total $sql_obj = new sql_query(); foreach ($traffic_types as $type) { $sql_obj->string = "SELECT '" . $this->data["total"]["total"] . "' + '" . $this->data["total"][$type] . "' as totalusage"; $sql_obj->execute(); $sql_obj->fetch_array(); $this->data["total"]["total"] = $sql_obj->data[0]["totalusage"]; } unset($sql_obj); log_write("debug", "service_usage_traffic", "Total usage for all addresses in the date range is " . $this->data["total"] . " bytes"); /* Disconnect from database */ $obj_traffic_db_sql->session_terminate(); unset($obj_traffic_db_sql); break; default: /* Unknown DB type, we should fail. */ log_write("error", "debug", "External DB type " . $GLOBALS["config"]["SERVICE_TRAFFIC_DB_TYPE"] . " is not supported."); return 0; break; } // end of switch between DB types } // end of external data source /* Generate formatted usage We have the number of raw units, we now need to generate the number of human readable/formatted units from this figure. */ log_write("debug", "service_usage_traffic", "Generating formatted usage totals"); $this->data["numrawunits"] = sql_get_singlevalue("SELECT numrawunits as value FROM service_units WHERE id='" . $this->obj_service->data["units"] . "' LIMIT 1"); if (!$this->data["numrawunits"]) { log_debug("service_usage_traffic", "Error: Unable to fetch number of raw units for the units type"); return 0; } foreach (array_keys($this->data["total"]) as $type) { // calculate $sql_obj = new sql_query(); $sql_obj->string = "SELECT '" . $this->data["total"][$type] . "' / '" . $this->data["numrawunits"] . "' as value"; $sql_obj->execute(); $sql_obj->fetch_array(); // format & return $this->data["total_byunits"][$type] = round($sql_obj->data[0]["value"], 2); } log_write("debug", "service_usage_traffic", "Total traffic usage for period is " . $this->data["total_byunits"]["total"] . ""); /* Complete */ return 1; }