/** * Get the amount incurred after a discount * * @param bool $format * * @return mixed */ public function getDiscountAmount($format = true) { if ($format) { return format_money($this->discount->product($this)); } return $this->discount->product($this)->getAmount(); }
function quotes_render_summarybox($id) { log_debug("inc_quotes", "quotes_render_summarybox({$id})"); // fetch quote information $sql_obj = new sql_query(); $sql_obj->string = "SELECT code_quote, amount_total, date_validtill, date_sent, sentmethod FROM account_quotes WHERE id='{$id}' LIMIT 1"; $sql_obj->execute(); if ($sql_obj->num_rows()) { $sql_obj->fetch_array(); if ($sql_obj->data[0]["amount_total"] == 0) { print "<table width=\"100%\" class=\"table_highlight_important\">"; print "<tr>"; print "<td>"; print "<b>Quote " . $sql_obj->data[0]["code_quote"] . " has no items on it</b>"; print "<p>This quote needs to have some items added to it using the links in the nav menu above.</p>"; print "</td>"; print "</tr>"; print "</table>"; } else { if (time_date_to_timestamp($sql_obj->data[0]["date_validtill"]) <= time()) { print "<table width=\"100%\" class=\"table_highlight_important\">"; print "<tr>"; print "<td>"; print "<p><b>Quote " . $sql_obj->data[0]["code_quote"] . " has now expired and is no longer valid.</b></p>"; print "</td>"; print "</tr>"; print "</table>"; } else { print "<table width=\"100%\" class=\"table_highlight_important\">"; print "<tr>"; print "<td>"; print "<b>Quote " . $sql_obj->data[0]["code_quote"] . " is currently valid.</b>"; print "<table cellpadding=\"4\">"; print "<tr>"; print "<td>Quote Total:</td>"; print "<td>" . format_money($sql_obj->data[0]["amount_total"]) . "</td>"; print "</tr>"; print "<tr>"; print "<td>Valid Until:</td>"; print "<td>" . $sql_obj->data[0]["date_validtill"] . "</td>"; print "</tr>"; print "<tr>"; print "<td>Date Sent:</td>"; if ($sql_obj->data[0]["sentmethod"] == "") { print "<td><i>Has not been sent to customer</i></td>"; } else { print "<td>" . $sql_obj->data[0]["date_sent"] . " (" . $sql_obj->data[0]["sentmethod"] . ")</td>"; } print "</tr>"; print "</tr></table>"; print "</td>"; print "</tr>"; print "</table>"; } } print "<br>"; } }
/** * @return mixed */ public function getDataTable() { $products = $this->products->with(['category', 'subcategory', 'brand', 'reviews'])->select('*'); return Datatables::of($products)->addColumn('edit', function ($product) { return link_to(route('backend.products.edit', ['id' => $product->id]), 'Edit', ['data-target-model' => $product->id, 'class' => 'btn btn-xs btn-primary']); })->editColumn('price', function ($product) { return format_money($product->price); })->make(true); }
function service_render_summarybox($id_service) { log_debug("inc_service", "service_render_summarybox({$id_service})"); $sql_obj = new sql_query(); $sql_obj->string = "SELECT name_service,\n\t\t\t\t\t\tactive,\n\t\t\t\t\t\tservice_types.name as type_name,\n\t\t\t\t\t\tservice_groups.group_name as group_name,\n\t\t\t\t\t\tprice,\n\t\t\t\t\t\tdiscount\n\t\t\t\t\tFROM services\n\t\t\t\t\tLEFT JOIN service_types ON service_types.id = services.typeid\n\t\t\t\t\tLEFT JOIN service_groups ON service_groups.id = services.id_service_group\n\t\t\t\t\tWHERE services.id='{$id_service}' LIMIT 1"; $sql_obj->execute(); if ($sql_obj->num_rows()) { $sql_obj->fetch_array(); if ($sql_obj->data[0]["active"]) { // service is enabled print "<table width=\"100%\" class=\"table_highlight_open\">"; print "<tr>"; print "<td>"; print "<b>Service " . $sql_obj->data[0]["name_service"] . " is enabled.</b>"; print "<table cellpadding=\"4\">"; print "<tr>"; print "<td>Service Type:</td>"; print "<td>" . $sql_obj->data[0]["type_name"] . "</td>"; print "</tr>"; print "<tr>"; print "<td>Service Group:</td>"; print "<td>" . $sql_obj->data[0]["group_name"] . "</td>"; print "</tr>"; if ($sql_obj->data[0]["discount"]) { // work out the price after discount $discount_calc = $sql_obj->data[0]["discount"] / 100; $discount_calc = $sql_obj->data[0]["price"] * $discount_calc; print "<tr>"; print "<td>Base Plan Price:</td>"; print "<td>" . format_money($sql_obj->data[0]["price"] - $discount_calc) . " (discount of " . format_money($discount_calc) . " included)</td>"; print "</tr>"; } else { print "<tr>"; print "<td>Base Plan Price:</td>"; print "<td>" . format_money($sql_obj->data[0]["price"]) . "</td>"; print "</tr>"; } print "</table>"; print "</td>"; print "</tr>"; print "</table>"; } else { // service is not yet enabled print "<table width=\"100%\" class=\"table_highlight_important\">"; print "<tr>"; print "<td>"; print "<b>Service " . $sql_obj->data[0]["name_service"] . " is inactive.</b>"; print "<p>This service is currently unconfigured, you need to setup the service plan before it will be activated.</p>"; print "</td>"; print "</tr>"; print "</table>"; } print "<br>"; } }
public function popoverReceiptPreview($receipt_id) { $r = Receipt::where('id', $receipt_id)->first(); $data['transaction'] = Receipt::buildReceipt($receipt_id); $data['created_date'] = ng_date_format($r->created_at); $data['created_time'] = ng_time_format($r->created_at); $data['sales_person'] = istr::title(User::where('id', $r->user_id)->first()->name); $data['total_amount_due'] = format_money($r->receipt_worth); $data['total_amount_tendered'] = format_money($r->amount_tendered); $data['change'] = format_money($r->change); $s = Salelog::where('receipt_id', $receipt_id)->get()->toArray(); //$s = Salelog::where('receipt_id', $receipt_id)->get(); $data['items'] = $s; //tt($data['items']->discount); //$data['items']->unitprice = $data['items']->unitprice - (($data['items']->unitprice / 100) * (($data['items']->discount > 0) ? $data['items']->discount : 1 )); //tt($data['items']); return View::make('admin.quick_receipt_preview', $data); }
function printValue($val, $h, $css) { $align = ''; // Maybe formalise 'time_length' filter, but check SQL pre-filter also if ($h['filter_special'] == 'time_length') { // $val = format_time_interval_prefs($val); $val = format_time_interval($val, true, '%.2f'); if (!$val) { $val = 0; } } elseif ($h['description'] == 'time_input_length') { $val = format_time_interval($val, true, '%.2f'); if (!$val) { $val = 0; } } switch ($h['filter']) { case 'date': // we leave the date in 0000-000-00 00:00:00 format break; case 'currency': if ($val) { $val = format_money($val); } else { $val = 0; } break; case 'number': $align = 'align="right"'; if (!$val) { $val = 0; } break; } if (is_numeric($val)) { echo $val . ", "; } else { // escape " character (csv) $val = str_replace('"', '""', $val); echo '"' . $val . '" , '; } }
function printValue($val, $h, $css) { $align = ''; // Maybe formalise 'time_length' filter, but check SQL pre-filter also if ($h['filter_special'] == 'time_length') { // $val = format_time_interval_prefs($val); $val = format_time_interval($val, true, '%.2f'); if (!$val) { $val = 0; } } elseif ($h['description'] == 'time_input_length') { $val = format_time_interval($val, true, '%.2f'); if (!$val) { $val = 0; } } switch ($h['filter']) { case 'date': if ($val) { $val = format_date($val, 'short'); } break; case 'currency': if ($val) { $val = format_money($val); } else { $val = 0; } break; case 'number': $align = 'align="right"'; if (!$val) { $val = 0; } break; } echo '<td ' . $align . ' ' . $css . '>' . $val . "</td>\n"; }
echo $value["total_count"]; ?> </p> <!--<span class="more"><span class="inner"></span></span>--> </div> </div> </a> </div> </div><?php } } ?> <div class="row"><!--总额行--> <div class="col-xs-12"><!--总额列--> <p class="date">订单总额:<?php echo format_money($order[count]); ?> 元</p> <div class="clr"></div> </div> <div class="col-xs-12"> <p class="shipped"><?php echo $order_data["order_status"]; ?> </p> </div> </div> </div> </div><?php } }
<h4>Previous Invoices</h4> <table> <?php if ($invoices) { foreach ($invoices as $invoice) { ?> <tr> <td class="date"><?php echo Date_Difference::getString(new DateTime(date("m/d/Y H:i:s", $invoice->date))); ?> </td> <td class="status"> </td> <td class="amount"><?php echo format_money($invoice->total_in_cents); ?> </td> <td class="invoice"><a href="<?php echo g_url('billing/account/getinvoice/' . $invoice->account_code . '/' . $invoice->id); ?> "><?php echo $invoice->invoice_number == "" ? "Not yet invoiced" : $invoice->invoice_number; ?> </a></td> </tr> <?php } //end foreach } else { echo "No invoices yet!";
/** * getDetails * * provides a lookup function for details on a members plan. Currently it * pulls out and sends information to start and end date, cost and state * * @author magicandmight * @param none */ public function getDetails() { $this->load->helper('date'); $this->load->helper("money_helper.php"); if ($subscription = self::getSubscription($this->account->account_code)) { $response = array(); $response["state"] = $subscription->state; $response["quantity"] = $subscription->quantity; $response["cost"] = format_money($subscription->total_amount_in_cents); $datestring = "%F %j%S"; $response["trial_end"] = mdate($datestring, $subscription->current_period_started_at); $response["end_date"] = mdate($datestring, $subscription->current_period_ends_at); $response["start_date"] = mdate($datestring, $subscription->current_period_started_at); } else { $response = array(); } return $response; }
echo $value["actionUrl"]["look"]; ?> "><?php echo $value["nickname"]; ?> </a></td> <td><?php echo format_money($achievement[$key]['totalGoodsFee']); ?> </td> <td><?php echo format_money($achievement[$key]['_line']['totalGoodsFee']); ?> </td> <td><?php echo format_money($achievement[$key]['_line']['totalGoodsFee'] + $achievement[$key]['totalGoodsFee']); ?> </td> <td><?php echo date("y/m/d H:s", $value["subscribe_time"]); ?> </td> <td><a onclick="#" href="<?php echo $value["actionUrl"]["parent_look"]; ?> "><?php echo $value["_parentname"]["nickname"]; ?> </a></td> <td><?php echo $value["phone_number"];
public function saveEdit() { $all = Input::all(); $id = $all['id']; unset($all['id']); //Lets update the table Bankentry::find($id)->update($all); //Lets fetch the updated data and return it through ajax //$update = Bankentry::where('id', $id)->get()->toArray(); $all['id'] = $id; if (isset($all['deposit_date'])) { $all['deposit_date'] = custom_date_format('M j, Y', $all['deposit_date']); } if (isset($all['amount'])) { $all['amount'] = format_money($all['amount']); } $data['status'] = 'success'; $data['message'] = $all; return Response::json($data); }
</div> <div class="col-xs-8"> <a class="goods-list-content" href="<?php echo $goodsDetailUrl; ?> ?goodsId=<?php echo $goods["id"]; ?> "><span class="name"><?php echo $goods["name"]; ?> </span></a> <div class="row row1"> <div class="col-xs-6 left1"> <span class="money"> ¥<?php echo format_money($goods['purchasing_price']); ?> </span> </div> <div class="col-xs-6 right1"> <img alt="140x140" width="20px" height="20px" src="<?php echo $goods["source"]["icon_url"]; ?> " alt="" class="img-circle"> <?php echo $goods["logistics_mode"]["name"]; ?> </div> </div> <?php if ($customerType == "1") {
function render_pdf() { // start the PDF object $template_pdf = new template_engine_latex(); // load template $template_pdf->prepare_load_template("templates/latex/report_balancesheet.tex"); /* Fill in template fields */ // company logo $template_pdf->prepare_add_file("company_logo", "png", "COMPANY_LOGO", 0); // mode $template_pdf->prepare_add_field("mode", $this->mode); // dates $template_pdf->prepare_add_field("date_end", time_format_humandate($this->date_end)); $template_pdf->prepare_add_field("date_created", time_format_humandate()); // totals $template_pdf->prepare_add_field("amount_total_current_earnings", $this->data_totals["current_earnings"]); $template_pdf->prepare_add_field("amount_total_assets", $this->data_totals["assets"]); $template_pdf->prepare_add_field("amount_total_liabilities", $this->data_totals["liabilities"]); $template_pdf->prepare_add_field("amount_total_equity", $this->data_totals["equity"]); $template_pdf->prepare_add_field("amount_total_liabilities_and_equity", $this->data_totals["liabilities_and_equity"]); // asset data $structure_main = NULL; foreach ($this->data_assets as $itemdata) { $structure = array(); $structure["name_chart"] = $itemdata["code_chart"] . " -- " . $itemdata["description"]; $structure["amount"] = format_money($itemdata["amount"]); $structure_main[] = $structure; } $template_pdf->prepare_add_array("table_assets", $structure_main); // liabilities data $structure_main = NULL; foreach ($this->data_liabilities as $itemdata) { $structure = array(); $structure["name_chart"] = $itemdata["code_chart"] . " -- " . $itemdata["description"]; $structure["amount"] = format_money($itemdata["amount"]); $structure_main[] = $structure; } $template_pdf->prepare_add_array("table_liabilities", $structure_main); // equity data $structure_main = NULL; foreach ($this->data_equity as $itemdata) { $structure = array(); $structure["name_chart"] = $itemdata["code_chart"] . " -- " . $itemdata["description"]; $structure["amount"] = format_money($itemdata["amount"]); $structure_main[] = $structure; } $template_pdf->prepare_add_array("table_equity", $structure_main); /* Output PDF */ // perform string escaping for latex $template_pdf->prepare_escape_fields(); // fill template $template_pdf->prepare_filltemplate(); // generate PDF output $template_pdf->generate_pdf(); // display PDF print $template_pdf->output; // foreach ($template_pdf->processed as $line) // { // print $line; // } }
function format_dollars($str) { return format_money($str, false); }
<div class="checkbox list-group-item"> <label> <input type="checkbox" id="coupon<?php echo $value["id"]; ?> " class="coupon" type="checkbox" data-id="<?php echo $value["id"]; ?> " data-value="<?php echo $value["cover"]; ?> " data-show-value="<?php echo format_money($value['cover']); ?> "> 面额:<?php echo format_money($value['cover']); ?> 使用期限:<?php echo date("Y-m-d H:i", $value['end_time']); ?> </label> </div><?php } } ?> </div> </div> <div class="border" bgcolor="#ffffff">本次最多可使用<font id="totalCount" data-value="<?php echo $i; ?> "><?php
} // do we need to generate a setup fee? if ($data["price_setup"] != "0.00" && $data["active"] == 1) { $obj_customer_order = new customer_orders(); $obj_customer_order->id = $obj_customer->id; $obj_customer_order->load_data(); $obj_customer_order->data_orders["date_ordered"] = date("Y-m-d"); $obj_customer_order->data_orders["type"] = "service"; $obj_customer_order->data_orders["customid"] = $obj_customer->obj_service->id; $obj_customer_order->data_orders["quantity"] = "1"; $obj_customer_order->data_orders["price"] = $data["price_setup"]; $obj_customer_order->data_orders["discount"] = $data["discount_setup"]; $obj_customer_order->data_orders["description"] = "Setup Fee: " . $data["name_service"] . ""; if (!$obj_customer_order->action_update_orders()) { log_write("error", "process", "An unexpected error occured whilst attempting to add an order item to the customer"); } else { log_write("notification", "process", "Added setup fee of " . format_money($obj_customer_order->data_orders["amount"]) . " to customer orders, this will then be billed automatically."); } } } // return to services page header("Location: ../index.php?page=customers/service-edit.php&id_customer=" . $obj_customer->id . "&id_service_customer=" . $obj_customer->id_service_customer . ""); exit(0); } ///////////////////////// } else { // user does not have perms to view this page/isn't logged on error_render_noperms(); header("Location: ../index.php?page=message.php"); exit(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(); }
?> <?php if (is_array($MenuList)) { foreach ($MenuList as $key => $value) { ?> <tr> <td class="a"><?php echo $value["id"]; ?> </td> <td class="a"><?php echo $value["name"]; ?> </td> <td style='text-align: right' class="a"><?php echo format_money($value['purchasing_price']); ?> </td> <td><?php echo $value["sales_volume"]; ?> </td> <td><?php echo $value["reorder"]; ?> </td> <td> <a href="<?php echo $value["actionUrl"]["onSheleves"]; ?> ">
echo htmlentities($project['name']); ?> </b> <a href="<?php echo projurl($project["id"]); ?> ">[go]</a></nobr> <div id="browse-<?php echo $project["id"]; ?> -div" class=folded> <?php echo formatText(ereg_replace("\n.*", "", $project["reqmts"])); ?> <p> <b>Bounty:</b> <nobr><?php echo htmlentities(format_money($project["bounty"])); ?> </nobr> <?php if (ereg("[1-9]", $project["bounty"]) && !ereg("^[0-9]*{$GLOBALS['pref_currency']}\$", $project["bounty"])) { ?> <nobr>(Approx. <?php echo htmlentities(convert_money($project["bounty"])); ?> )</nobr><?php } ?> </p> <a href="<?php echo projurl($project["id"]); ?>
(at your option) any later version. Fossfactory-src is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Fossfactory-src. If not, see <http://www.gnu.org/licenses/>. */ $p = scrub($_REQUEST["p"]); $amt = "{$_REQUEST['amt']}"; apply_template("Project Created", array(array("name" => "Project Created", "href" => "createdproject.php?p={$p}&amt=" . urlencode($amt)))); ?> <div class=results> Thank you for creating a new FOSS Factory project. Your payment of <?php echo format_money($amt); ?> has been received. A receipt has been emailed to you. You may log into your account at <a href="http://www.paypal.com/">www.paypal.com</a> to view details of the transaction. <p> <a href="<?php echo projurl($p); ?> ">Continue to Project Page</a> </p>
<table cellspacing=0 cellpadding=5 id="currency_table"> <tr><th align=left>Currency</th><th align=right>Amount</th></tr> <?php foreach ($currencies as $code => $currency) { $amount = currency_value($reserve, $code); if (!ereg("[1-9]", $amount)) { continue; } ?> <tr> <td align=left><?php echo $currency["name"]; ?> </td> <td align=right><?php echo format_money($amount . $code, $code); ?> </td> </tr> <?php } ?> </table> <?php } ?> <hr size="1" /> <h2>Add Funds</h2> <p> Money in your reserve can be used to sponsor projects.
<div class="col-xs-6"> 下线业绩:<?php echo format_money($achievement['line']['_totalGoodsFee']); ?> <br /> 下线佣金基数:<?php echo format_money($achievement['line']['_totalLineFee']); ?> <br /> 下线佣金系数:<?php echo $achievement['_line_radio']['line_ratio']; ?> <br /> 下线实际佣金:<?php $lineFee = floor($achievement['line']['_totalDirectFee'] * $achievement['_line_radio']['line_ratio']); echo format_money($line); ?> <br /> </div> </div> <div class="row xuxian"></div> <div class="row"> <a href="<?php echo $wangqiurl; ?> "> <div class="col-xs-10 title" style="padding-bottom: 20px;"> <span>我的往期业绩</span> </div> <div class="col-xs-2 text-center"> <span class="glyphicon glyphicon-chevron-right"></span>
" target="_blank"> <?php echo $student->child_key; ?> </a> </td> <td > <a href="<?php echo site_url("child/view/{$student->student_id}"); ?> " target="_blank"> <?php echo get_display_name($student->name, $student->alias); ?> </a> </td> <td ><?php echo format_money($student->invoice_amount - $student->invoice_balance, $current_currency); ?> </td> <td > <a href="<?php echo site_url("payment/summary_of_account/{$student->student_id}"); ?> " getLink="<?php echo site_url("payment/summary_of_account/{$student->student_id}"); ?> " class="backable_link da-button gray">Summary</a> <a href="<?php echo site_url("payment/create_receipt/{$student->student_id}"); ?> " getLink="<?php echo site_url("payment/create_receipt/{$student->student_id}");
function prepare_data($data) { log_debug("invoice_items", "Executing prepare_data(array)"); // we must have supplied item data if (!$this->type_item) { log_write("error", "invoice_items", "No item_type value supplied, unable to process data."); return 0; } // process for the item type. switch ($this->type_item) { case "standard": /* STANDARD ITEMS */ // very simple, just copy the data across foreach (array_keys($data) as $i) { $this->data[$i] = $data[$i]; } // $this->data["amount"] = $data["amount"]; // $this->data["chartid"] = $data["chartid"]; // $this->data["description"] = $data["description"]; break; case "product": /* PRODUCT ITEMS */ // save information $this->data["price"] = $data["price"]; $this->data["quantity"] = $data["quantity"]; $this->data["units"] = $data["units"]; $this->data["customid"] = $data["customid"]; $this->data["description"] = $data["description"]; $this->data["discount"] = $data["discount"]; // calculate the total amount $this->data["amount"] = $data["price"] * $data["quantity"]; // apply any discounts if ($this->data["discount"]) { // convert percentage to float $discount_calc = 1 - $this->data["discount"] / 100; // apply discount $this->data["amount"] = $this->data["amount"] * $discount_calc; } // get the chart for the product - this will be the account_sales // for ar/quotes, or account_purchase for AP invoices $sql_obj = new sql_query(); if ($this->type_invoice == "ap") { $sql_obj->string = "SELECT account_purchase as account FROM products WHERE id='" . $this->data["customid"] . "' LIMIT 1"; } else { $sql_obj->string = "SELECT account_sales as account FROM products WHERE id='" . $this->data["customid"] . "' LIMIT 1"; } $sql_obj->execute(); if ($sql_obj->num_rows()) { $sql_obj->fetch_array(); $this->data["chartid"] = $sql_obj->data[0]["account"]; } else { if (!$_SESSION["error"]["productid-error"]) { $_SESSION["error"]["message"][] = "The requested product does not exist!"; $_SESSION["error"]["productid-error"] = 1; return 0; } } break; case "time": /* TIME ITEMS We need to get the number of billable hours, then calculate the total charge for the item. The supplied price is the cost per hour, and the supplied productid provides the information for where the time should be billed to. */ // a time item can only be added to an AR transactions if ($this->type_invoice != "ar") { $_SESSION["error"]["message"][] = "You can only add time invoice items to AR invoices."; return 0; } // save information $this->data["price"] = $data["price"]; $this->data["customid"] = $data["customid"]; $this->data["timegroupid"] = $data["timegroupid"]; $this->data["description"] = $data["description"]; $this->data["units"] = $data["units"]; $this->data["discount"] = $data["discount"]; // fetch the number of billable hours for the supplied timegroupid $sql_obj = new sql_query(); $sql_obj->string = "SELECT SUM(time_booked) as time_billable FROM timereg WHERE groupid='" . $this->data["timegroupid"] . "' AND billable='1'"; $sql_obj->execute(); if ($sql_obj->num_rows()) { // work out the number of hours and excess minutes $sql_obj->fetch_array(); $minutes = $sql_obj->data[0]["time_billable"] / 60; $hours = sprintf("%d", $minutes / 60); $excess_minutes = sprintf("%02d", $minutes - $hours * 60); // convert minutes to base-10 numbering systems // eg: 15mins becomes 0.25 $excess_minutes = $excess_minutes / 60; // set the quantity $this->data["quantity"] = $hours + $excess_minutes; } else { $_SESSION["error"]["message"][] = "Invalid time group supplied!"; return 0; } // calculate the total amount $this->data["amount"] = $this->data["price"] * $this->data["quantity"]; // apply any discounts if ($this->data["discount"]) { // convert percentage to float $discount_calc = 1 - $this->data["discount"] / 100; // apply discount $this->data["amount"] = $this->data["amount"] * $discount_calc; } // get the chart for the product $sql_obj = new sql_query(); $sql_obj->string = "SELECT account_sales FROM products WHERE id='" . $this->data["customid"] . "' LIMIT 1"; $sql_obj->execute(); if ($sql_obj->num_rows()) { $sql_obj->fetch_array(); $this->data["chartid"] = $sql_obj->data[0]["account_sales"]; } else { if (!$_SESSION["error"]["productid-error"]) { $_SESSION["error"]["message"][] = "The requested product does not exist!"; $_SESSION["error"]["productid-error"] = 1; return 0; } } break; case "service": case "service_usage": /* SERVICE ITEMS */ // a service item can only be added to an AR transactions if ($this->type_invoice != "ar") { log_write("error", "inc_invoices", "You can only add service invoice items to AR invoices."); return 0; } // save information $this->data["price"] = $data["price"]; $this->data["quantity"] = $data["quantity"]; $this->data["units"] = $data["units"]; $this->data["customid"] = $data["customid"]; $this->data["description"] = $data["description"]; $this->data["discount"] = $data["discount"]; // extra ID $this->data["id_service_customer"] = $period_usage_data["id_service_customer"]; $this->data["id_period"] = $period_usage_data["id"]; // service specific $this->data["cdr_billgroup"] = $data["cdr_billgroup"]; // calculate the total amount $this->data["amount"] = $data["price"] * $data["quantity"]; // apply any discounts if ($this->data["discount"]) { // convert percentage to float $discount_calc = 1 - $this->data["discount"] / 100; // apply discount $this->data["amount"] = $this->data["amount"] * $discount_calc; } // get the chart for the service - this will be the account_sales // for ar/quotes, or account_purchase for AP invoices $sql_obj = new sql_query(); $sql_obj->string = "SELECT chartid as account FROM services WHERE id='" . $this->data["customid"] . "' LIMIT 1"; $sql_obj->execute(); if ($sql_obj->num_rows()) { $sql_obj->fetch_array(); $this->data["chartid"] = $sql_obj->data[0]["account"]; } else { log_write("error", "inc_invoice", "Unknown service, unable to add item."); } break; case "payment": /* PAYMENT ITEM */ // fetch information from form $this->data["date_trans"] = $data["date_trans"]; $this->data["amount"] = $data["amount"]; $this->data["chartid"] = $data["chartid"]; $this->data["source"] = $data["source"]; $this->data["description"] = $data["description"]; // check credit amount if ($this->data["chartid"] == "credit") { if ($this->type_invoice == "ar") { $id_customer = sql_get_singlevalue("SELECT customerid as value FROM account_ar WHERE id='" . $this->id_invoice . "' LIMIT 1"); $credit_balance = sql_get_singlevalue("SELECT SUM(amount_total) as value FROM customers_credits WHERE id_customer='" . $id_customer . "' AND id_custom!='" . $this->id_item . "'"); } else { $id_vendor = sql_get_singlevalue("SELECT vendorid as value FROM account_ap WHERE id='" . $this->id_invoice . "' LIMIT 1"); $credit_balance = sql_get_singlevalue("SELECT SUM(amount_total) as value FROM vendor_credits WHERE id_vendor='" . $id_vendor . "' AND id_custom!='" . $this->id_item . "'"); } if ($this->data["amount"] > $credit_balance) { log_write("error", "inc_invoice_items", "Unable to accept credit payment of " . format_money($this->data["amount"]) . ", credit balance is currently " . format_money($credit_balance) . ""); } } break; case "credit": /* CREDIT ITEMS */ // very simple, just copy the data across foreach (array_keys($data) as $i) { $this->data[$i] = $data[$i]; } break; default: log_write("error", "inc_invoice", "Unknown item type provided."); return 0; break; } // complete return 1; }
echo lang('lang_last_login'); ?> : <?php echo format_date($row['user_last_login']); ?> </span><br /> </div> </fieldset> <fieldset> <legend><?php echo lang('lang_stats'); ?> </legend> <ul id="stats"> <li>Total Spent: <?php echo format_money($total_order_amount); ?> </li> <li>Total Orders: <?php echo $total_orders; ?> </li> <li>Listings Placed: <?php echo $row['user_listings']; ?> </li> <li>Active Listings: <?php echo $active_listings; ?> </li> </ul>
<td id="sub_total" style="width:35%"><?php echo format_money(isset($sub_total) ? $sub_total : 0, $current_currency); ?> </td> </tr> <tr> <td style="width:65%; font-weidht:bold;">TAX</td> <td style="width:35%"><?php echo format_money(isset($sub_total) ? $sub_total * $invoice->tax / 100 : '0', $current_currency); ?> </td> </tr> <tr> <td style="width:65%; font-weidht:bold;">Total</td> <td style="width:35%"><?php echo format_money(isset($sub_total) ? $sub_total * $invoice->tax / 100 + $sub_total : '0', $current_currency); ?> </td> </tr> </table> </div> <div style="clear:both;"></div> </div> <div class="da-form-row"> <?php echo $invoice_settings['invoice_footer']; ?> </div> </div>
function render_pdf() { // start the PDF object $template_pdf = new template_engine_latex(); // load template $template_pdf->prepare_load_template("templates/latex/report_incomestatement.tex"); /* Fetch data + define fields */ // company logo $template_pdf->prepare_add_file("company_logo", "png", "COMPANY_LOGO", 0); // mode $template_pdf->prepare_add_field("mode", $this->mode); // dates $template_pdf->prepare_add_field("date_start", time_format_humandate($this->date_start)); $template_pdf->prepare_add_field("date_end", time_format_humandate($this->date_end)); $template_pdf->prepare_add_field("date_created", time_format_humandate()); // totals $template_pdf->prepare_add_field("amount_total_income", $this->data_totals["income"]); $template_pdf->prepare_add_field("amount_total_expense", $this->data_totals["expense"]); $template_pdf->prepare_add_field("amount_total_final", $this->data_totals["final"]); // income data $structure_main = NULL; foreach ($this->data_income as $itemdata) { $structure = array(); $structure["name_chart"] = $itemdata["code_chart"] . " -- " . $itemdata["description"]; $structure["amount"] = format_money($itemdata["amount"]); $structure_main[] = $structure; } $template_pdf->prepare_add_array("table_income", $structure_main); // income data $structure_main = NULL; foreach ($this->data_expense as $itemdata) { $structure = array(); $structure["name_chart"] = $itemdata["code_chart"] . " -- " . $itemdata["description"]; $structure["amount"] = format_money($itemdata["amount"]); $structure_main[] = $structure; } $template_pdf->prepare_add_array("table_expense", $structure_main); /* Output PDF */ // perform string escaping for latex $template_pdf->prepare_escape_fields(); // fill template $template_pdf->prepare_filltemplate(); // generate PDF output $template_pdf->generate_pdf(); // display PDF print $template_pdf->output; // foreach ($template_pdf->processed as $line) // { // print $line; // } }
function execute() { /* Define form structure */ $this->obj_form = new form_input(); $this->obj_form->formname = "transaction_view"; $this->obj_form->language = $_SESSION["user"]["lang"]; $this->obj_form->action = "accounts/gl/edit-process.php"; $this->obj_form->method = "post"; // general $structure = NULL; $structure["fieldname"] = "code_gl"; $structure["type"] = "input"; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "date_trans"; $structure["type"] = "date"; $structure["defaultvalue"] = date("Y-m-d"); $structure["options"]["req"] = "yes"; $this->obj_form->add_input($structure); $sql_struct_obj = new sql_query(); $sql_struct_obj->prepare_sql_settable("staff"); $sql_struct_obj->prepare_sql_addfield("id", "staff.id"); $sql_struct_obj->prepare_sql_addfield("label", "staff.staff_code"); $sql_struct_obj->prepare_sql_addfield("label1", "staff.name_staff"); $sql_struct_obj->prepare_sql_addorderby("staff_code"); $sql_struct_obj->prepare_sql_addwhere("id = 'CURRENTID' OR date_end = '0000-00-00'"); $structure = form_helper_prepare_dropdownfromobj("employeeid", $sql_struct_obj); $structure["options"]["req"] = "yes"; $structure["options"]["autoselect"] = "yes"; $structure["options"]["width"] = "600"; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "description"; $structure["type"] = "input"; $structure["options"]["width"] = "600"; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "description_useall"; $structure["type"] = "checkbox"; $structure["options"]["label"] = "Check this to use the description above as the description in all the rows below. Untick if you wish to have different messages for each transaction item."; $structure["defaultvalue"] = "on"; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "notes"; $structure["type"] = "textarea"; $structure["options"]["width"] = "600"; $structure["options"]["height"] = "50"; $this->obj_form->add_input($structure); /* Define transaction form structure */ // unless there has been error data returned, fetch all the transactions // from the DB, and work out the number of rows if (!isset($_SESSION["error"]["form"][$this->obj_form->formname])) { $sql_trans_obj = new sql_query(); $sql_trans_obj->string = "SELECT date_trans, amount_debit, amount_credit, chartid, source, memo FROM `account_trans` WHERE type='gl' AND customid='" . $this->id . "'"; $sql_trans_obj->execute(); if ($sql_trans_obj->num_rows()) { $sql_trans_obj->fetch_array(); $this->num_trans = $sql_trans_obj->data_num_rows + 1; } } else { $this->num_trans = @security_script_input('/^[0-9]*$/', $_SESSION["error"]["num_trans"]) + 1; } // ensure there are always 2 rows at least, additional rows are added if required (ie viewing // an existing transaction) or on the fly when needed by javascript UI. if ($this->num_trans < 2) { $this->num_trans = 2; } // transaction rows for ($i = 0; $i < $this->num_trans; $i++) { // account $structure = form_helper_prepare_dropdownfromdb("trans_" . $i . "_account", "SELECT id, code_chart as label, description as label1 FROM account_charts WHERE chart_type!='1' ORDER BY code_chart"); $structure["options"]["width"] = "200"; $this->obj_form->add_input($structure); // debit field $structure = NULL; $structure["fieldname"] = "trans_" . $i . "_debit"; $structure["type"] = "input"; $structure["options"]["width"] = "80"; $this->obj_form->add_input($structure); // credit field $structure = NULL; $structure["fieldname"] = "trans_" . $i . "_credit"; $structure["type"] = "input"; $structure["options"]["width"] = "80"; $this->obj_form->add_input($structure); // source $structure = NULL; $structure["fieldname"] = "trans_" . $i . "_source"; $structure["type"] = "input"; $structure["options"]["width"] = "100"; $this->obj_form->add_input($structure); // description $structure = NULL; $structure["fieldname"] = "trans_" . $i . "_description"; $structure["type"] = "textarea"; $this->obj_form->add_input($structure); // if we have data from a sql query, load it in if ($sql_trans_obj->data_num_rows) { if (isset($sql_trans_obj->data[$i]["chartid"])) { $this->obj_form->structure["trans_" . $i . "_debit"]["defaultvalue"] = $sql_trans_obj->data[$i]["amount_debit"]; $this->obj_form->structure["trans_" . $i . "_credit"]["defaultvalue"] = $sql_trans_obj->data[$i]["amount_credit"]; $this->obj_form->structure["trans_" . $i . "_account"]["defaultvalue"] = $sql_trans_obj->data[$i]["chartid"]; $this->obj_form->structure["trans_" . $i . "_source"]["defaultvalue"] = $sql_trans_obj->data[$i]["source"]; $this->obj_form->structure["trans_" . $i . "_description"]["defaultvalue"] = $sql_trans_obj->data[$i]["memo"]; } } } // total fields $structure = NULL; $structure["fieldname"] = "total_debit"; $structure["type"] = "hidden"; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "total_credit"; $structure["type"] = "hidden"; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "money_format"; $structure["type"] = "hidden"; $structure["defaultvalue"] = format_money(0); $this->obj_form->add_input($structure); // hidden $structure = NULL; $structure["fieldname"] = "id_transaction"; $structure["type"] = "hidden"; $structure["defaultvalue"] = $this->id; $this->obj_form->add_input($structure); $structure = NULL; $structure["fieldname"] = "num_trans"; $structure["type"] = "hidden"; $structure["defaultvalue"] = "{$this->num_trans}"; $this->obj_form->add_input($structure); // submit section $structure = NULL; $structure["fieldname"] = "submit"; $structure["type"] = "submit"; $structure["defaultvalue"] = "Save Changes"; $this->obj_form->add_input($structure); // fetch the general form data $this->obj_form->sql_query = "SELECT * FROM `account_gl` WHERE id='" . $this->id . "' LIMIT 1"; $this->obj_form->load_data(); // calculate totals for ($i = 0; $i < $this->num_trans; $i++) { @($this->obj_form->structure["total_debit"]["defaultvalue"] += $this->obj_form->structure["trans_" . $i . "_debit"]["defaultvalue"]); @($this->obj_form->structure["total_credit"]["defaultvalue"] += $this->obj_form->structure["trans_" . $i . "_credit"]["defaultvalue"]); } }
function service_invoices_generate($customerid = NULL) { log_debug("inc_services_invoicegen", "Executing service_invoices_generate({$customerid})"); /* Invoice Report Statistics */ $invoice_stats = array(); $invoice_stats["time_start"] = time(); $invoice_stats["total"] = 0; $invoice_stats["total_failed"] = 0; /* Run through all the customers */ $sql_customers_obj = new sql_query(); $sql_customers_obj->string = "SELECT id, code_customer, name_customer FROM customers"; if ($customerid) { $sql_customers_obj->string .= " WHERE id='{$customerid}' LIMIT 1"; } $sql_customers_obj->execute(); if ($sql_customers_obj->num_rows()) { $sql_customers_obj->fetch_array(); foreach ($sql_customers_obj->data as $customer_data) { /* Fetch all periods belonging to this customer which need to be billed. */ $sql_periods_obj = new sql_query(); $sql_periods_obj->string = "SELECT " . "services_customers_periods.id, " . "services_customers_periods.rebill, " . "services_customers_periods.invoiceid, " . "services_customers_periods.invoiceid_usage, " . "services_customers_periods.date_start, " . "services_customers_periods.date_end, " . "services_customers.date_period_first, " . "services_customers.date_period_next, " . "services_customers.date_period_last, " . "services_customers.id as id_service_customer, " . "services_customers.serviceid " . "FROM services_customers_periods " . "LEFT JOIN services_customers ON services_customers.id = services_customers_periods.id_service_customer " . "WHERE " . "services_customers.customerid='" . $customer_data["id"] . "' " . "AND (invoiceid = '0' OR rebill = '1')" . "AND date_billed <= '" . date("Y-m-d") . "'"; $sql_periods_obj->execute(); if ($sql_periods_obj->num_rows()) { $sql_periods_obj->fetch_array(); /* BILL CUSTOMER This customer has at least one service that needs to be billed. We need to create a new invoice, and then process each service, adding the services to the invoice as items. */ /* Start Transaction (one transaction per invoice) */ $sql_obj = new sql_query(); $sql_obj->trans_begin(); /* Create new invoice */ $invoice = new invoice(); $invoice->type = "ar"; $invoice->prepare_code_invoice(); $invoice->data["customerid"] = $customer_data["id"]; $invoice->data["employeeid"] = 1; // set employee to the default internal user $invoice->prepare_date_shift(); if (!$invoice->action_create()) { log_write("error", "services_invoicegen", "Unexpected problem occured whilst attempting to create invoice."); $sql_obj->trans_rollback(); return 0; } $invoiceid = $invoice->id; $invoicecode = $invoice->data["code_invoice"]; unset($invoice); /* Create Service Items We need to create an item for basic service plan - IE: the regular fixed fee, and another item for any excess usage. */ foreach ($sql_periods_obj->data as $period_data) { /* TODO: We should be able to re-bill usage here when needed with a clever cheat - if we load the periods to be billed, we can then ignore the plan item, and rebill the usage item. */ $period_data["mode"] = "standard"; if ($period_data["rebill"]) { if (!$period_data["invoiceid_usage"] && $period_data["invoiceid"]) { // the selected period has been billed, but the usage has been flagged for rebilling - we need to *ignore* the base plan // item and only bill for the usage range. $period_data["mode"] = "rebill_usage"; } } // fetch service details $obj_service = new service_bundle(); $obj_service->option_type = "customer"; $obj_service->option_type_id = $period_data["id_service_customer"]; if (!$obj_service->verify_id_options()) { log_write("error", "customers_services", "Unable to verify service ID of " . $period_data["id_service_customer"] . " as being valid."); return 0; } $obj_service->load_data(); $obj_service->load_data_options(); // ratio is used to adjust prices for partial periods $ratio = 1; if ($obj_service->data["billing_mode_string"] == "monthend" || $obj_service->data["billing_mode_string"] == "monthadvance" || $obj_service->data["billing_mode_string"] == "monthtelco") { log_debug("services_invoicegen", "Invoice bills by month date"); /* 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, or consist of less than the full month, depending on the operational mode. SERVICE_PARTPERIOD_MODE == seporate If a service is started on 2008-01-09, the end date of the billing period will be 2008-01-31, which is less than one month - 22 days. We can calculate with: ( standard_cost / normal_month_num_days ) * num_days_in_partial_month == total_amount SERVICE_PARTPERIOD_MODE == merge 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. We can calculate with: ( standard_cost / normal_month_num_days ) * num_days_in_partial_month == extra_amount total_amount = (extra_amount + normal_amount) Note: we do not increase included units for licenses service types, since these need to remain fixed and do not scale with the month. */ // check if the period start date is not the first of the month - this means that the period // is an inital partial period. if (time_calculate_daynum($period_data["date_start"]) != "01") { // very first billing month log_write("debug", "services_invoicegen", "First billing month for this service, adjusting pricing to suit days."); // figure out normal period length - rememeber, whilst service may be configured to align monthly, it needs to // handle pricing calculations for variations such as month, year, etc. // get the number of days of a regular period - this correctly handles, month, year, etc // 1. generate the next period dates, this will be of regular length // 2. calculate number of days $tmp_dates = service_period_dates_generate($period_data["date_period_next"], $obj_service->data["billing_cycle_string"], $obj_service->data["billing_mode_string"]); $regular_period_num_days = sql_get_singlevalue("SELECT DATEDIFF('" . $tmp_dates["end"] . "', '" . $tmp_dates["start"] . "') as value"); // process for short/long periods // if ($GLOBALS["config"]["SERVICE_PARTPERIOD_MODE"] == "seporate") { log_write("debug", "services_invoicegen", "Adjusting for partial month period (SERVICE_PARTPERIOD_MODE == seporate)"); // work out the total number of days in partial month $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", "services_invoicegen", "Short initial billing period of {$short_month_days_short} days"); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $regular_period_num_days * $short_month_days_short; // calculate ratio $ratio = $short_month_days_short / $regular_period_num_days; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); } else { log_write("debug", "services_invoicegen", "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("services_invoicegen", "{$extra_month_days_extra} additional days ontop of started billing period"); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $regular_period_num_days * $extra_month_days_extra + $obj_service->data["price"]; // calculate ratio $ratio = ($extra_month_days_extra + $extra_month_days_total) / $regular_period_num_days; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle extended period."); } } } /* Service Last Period Handling If this is the last period for a service, it may be of a different link to the regular full period term - this effects both month and period based services. */ if ($period_data["date_period_last"] != "0000-00-00") { log_write("debug", "services_invoicegen", "Service has a final period date set (" . $period_data["date_period_last"] . ")"); if ($period_data["date_period_last"] == $period_data["date_end"] || time_date_to_timestamp($period_data["date_period_last"]) < time_date_to_timestamp($period_data["date_end"])) { log_write("debug", "services_invoicegen", "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", "services_invoicegen", "Short initial billing period of " . $time["new_days"] . " days rather than expected " . $time["orig_days"] . ""); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $time["orig_days"] * $time["new_days"]; // calculate ratio $ratio = $time["new_days"] / $time["orig_days"]; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); unset($time); } else { log_write("debug", "services_invoicegen", "Final service period is regular size, no adjustment required."); } } } /* Service Base Plan Item Note: There can be a suitation where we shouldn't charge for the base plan fee, when the period end date is older than the service first start date. This can happen due to the migration mode, which creates a usage-only period before the actual plan starts properly. */ if (time_date_to_timestamp($period_data["date_period_first"]) < time_date_to_timestamp($period_data["date_end"]) && $period_data["mode"] == "standard") { // date of the period is newer than the start date log_write("debug", "inc_service_invoicegen", "Generating base plan item for period " . $period_data["date_start"] . " to " . $period_data["date_end"] . ""); // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service"; $itemdata = array(); // chart ID $itemdata["chartid"] = $obj_service->data["chartid"]; // service ID $itemdata["customid"] = $obj_service->id; // no units apply $itemdata["units"] = ""; // description switch ($obj_service->data["typeid_string"]) { case "phone_single": $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " from " . $period_data["date_start"] . " to " . $period_data["date_end"] . " (" . $obj_service->data["phone_ddi_single"] . ")"; if ($obj_service->data["description"]) { $itemdata["description"] .= "\n\n"; $itemdata["description"] .= addslashes($obj_service->data["description"]); } break; default: $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " from " . $period_data["date_start"] . " to " . $period_data["date_end"]; if ($obj_service->data["description"]) { $itemdata["description"] .= "\n\n"; $itemdata["description"] .= addslashes($obj_service->data["description"]); } break; } // handle final periods if ($period_data["date_period_last"] != "0000-00-00" && $period_data["date_start"] == $period_data["date_end"]) { $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " service terminated as of " . $period_data["date_end"] . ""; } // is this service item part of a bundle? if it is, we shouldn't charge for the plan if (!empty($obj_service->data["id_bundle_component"])) { // no charge for the base plan, since this is handled by // the bundle item itself $itemdata["price"] = "0.00"; $itemdata["quantity"] = 1; $itemdata["discount"] = "0"; // append description $itemdata["description"] .= " (part of bundle)"; } else { // amount $itemdata["price"] = $obj_service->data["price"]; $itemdata["quantity"] = 1; $itemdata["discount"] = $obj_service->data["discount"]; } // create item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } else { log_write("debug", "inc_service_invoicegen", "Skipping base plan item generation, due to migration or rebill mode operation"); } /* Service Usage Items Create another item on the invoice for any usage, provided that the service type is a usage service */ $period_usage_data = array(); /* Depending on the service type, we need to handle the usage period in one of two ways: 1. Invoice usage for the current period that has just ended (periodend/monthend) 2. Invoice usage for the period BEFORE the current period. (periodtelco/monthtelco) */ if ($obj_service->data["billing_mode_string"] == "periodtelco" || $obj_service->data["billing_mode_string"] == "monthtelco") { log_write("debug", "service_invoicegen", "Determining previous period for telco-style usage billing (if any)"); if ($period_data["mode"] == "standard") { // fetch previous period (if any) - we can determine the previous by subtracting one day from the current period start date. $tmp_date = explode("-", $period_data["date_start"]); $period_usage_data["date_end"] = date("Y-m-d", mktime(0, 0, 0, $tmp_date[1], $tmp_date[2] - 1, $tmp_date[0])); } elseif ($period_data["mode"] == "rebill_usage") { // use the period as the usage period log_write("debug", "service_invoicegen", "Using the selected period " . $period_usage_data["date_end"] . " for rebill_usage mode"); $period_usage_data["date_end"] = $period_data["date_end"]; } // fetch period data to confirm previous period $sql_obj = new sql_query(); $sql_obj->string = "SELECT id, date_start, date_end FROM services_customers_periods WHERE id_service_customer='" . $period_data["id_service_customer"] . "' AND date_end='" . $period_usage_data["date_end"] . "' LIMIT 1"; $sql_obj->execute(); if ($sql_obj->num_rows()) { log_write("debug", "service_invoicegen", "Billing for seporate past usage period"); // there is a valid usage period $period_usage_data["active"] = "yes"; // fetch dates $sql_obj->fetch_array(); $period_usage_data["id_service_customer"] = $period_data["id_service_customer"]; $period_usage_data["id"] = $sql_obj->data[0]["id"]; $period_usage_data["date_start"] = $sql_obj->data[0]["date_start"]; $period_usage_data["date_end"] = $sql_obj->data[0]["date_end"]; // tracing log_write("debug", "service_invoicegen", "Current period is (" . $period_data["date_start"] . " to " . $period_data["date_end"] . "), usage period is (" . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ")"); // reset ratio $ratio = 1; /* TODO: This code is a replicant of the section further above for calculating partial periods and should really be functionalised as part of service usage continual improvements */ // 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("services_invoicegen", "Usage period service bills by month date"); if (time_calculate_daynum($period_usage_data["date_start"]) != "01") { // very first billing month log_write("debug", "services_invoicegen", "First billing month for this usage period, adjusting pricing to suit days."); if ($GLOBALS["config"]["SERVICE_PARTPERIOD_MODE"] == "seporate") { log_write("debug", "services_invoicegen", "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_usage_data["date_start"])); $short_month_days_short = $short_month_days_total - time_calculate_daynum($period_usage_data["date_start"]); log_write("debug", "services_invoicegen", "Short initial billing period of {$short_month_days_short} days"); // calculate ratio $ratio = $short_month_days_short / $short_month_days_total; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); } else { log_write("debug", "services_invoicegen", "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_usage_data["date_start"])); $extra_month_days_extra = $extra_month_days_total - time_calculate_daynum($period_usage_data["date_start"]); log_debug("services_invoicegen", "{$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", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle extended period."); } } } // end of calculate usage abnormal period if ($period_data["date_period_last"] != "0000-00-00") { log_write("debug", "services_invoicegen", "Service has a final period date set (" . $period_data["date_period_last"] . ")"); if ($period_data["date_period_last"] == $period_usage_data["date_end"] || time_date_to_timestamp($period_data["date_period_last"]) < time_date_to_timestamp($period_usage_data["date_end"])) { log_write("debug", "services_invoicegen", "Service is a final period, checking for time adjustment (if any)"); // fetch the regular end date $orig_dates = service_period_dates_generate($period_usage_data["date_start"], $obj_service->data["billing_cycle_string"], $obj_service->data["billing_mode_string"]); if ($orig_dates["end"] != $period_usage_data["date_end"]) { // work out the total number of days $time = NULL; $time["start"] = time_date_to_timestamp($period_usage_data["date_start"]); $time["end_orig"] = time_date_to_timestamp($orig_dates["end"]); $time["end_new"] = time_date_to_timestamp($period_usage_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", "services_invoicegen", "Short initial billing period of " . $time["new_days"] . " days rather than expected " . $time["orig_days"] . ""); // calculate correct base fee $obj_service->data["price"] = $obj_service->data["price"] / $time["orig_days"] * $time["new_days"]; // calculate ratio $ratio = $time["new_days"] / $time["orig_days"]; log_write("debug", "services_invoicegen", "Calculated service bill ratio of {$ratio} to handle short period."); unset($time); } else { log_write("debug", "services_invoicegen", "Final service period is regular size, no adjustment required."); } } } } else { log_write("debug", "service_invoicegen", "Not billing for past usage, as this appears to be the first plan period so no usage can exist yet"); } } else { log_write("debug", "service_invoicegen", "Using plan period as data usage period (" . $period_data["date_start"] . " to " . $period_data["date_end"] . ""); // use current period $period_usage_data["active"] = "yes"; $period_usage_data["id_service_customer"] = $period_data["id_service_customer"]; $period_usage_data["id"] = $period_data["id"]; $period_usage_data["date_start"] = $period_data["date_start"]; $period_usage_data["date_end"] = $period_data["date_end"]; } /* Create usage items if there is a valid usage period */ if (!empty($period_usage_data["active"])) { log_write("debug", "service_invoicegen", "Creating usage items due to active usage period"); switch ($obj_service->data["typeid_string"]) { case "generic_with_usage": /* GENERIC_WITH_USAGE This service is to be used for any non-traffic, non-time accounting service that needs to track usage. Examples of this could be counting the number of API requests, size of disk usage on a vhost, etc. */ log_write("debug", "service_invoicegen", "Processing usage items for generic_with_usage"); /* Usage Item Basics */ // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; /* Adjust Included Units to handle partial or extended periods */ if ($ratio != "1") { $obj_service->data["included_units"] = sprintf("%d", $obj_service->data["included_units"] * $ratio); } /* Fetch usage amount */ $usage_obj = new service_usage_generic(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; if ($usage_obj->load_data_service()) { $usage_obj->fetch_usagedata(); if ($usage_obj->data["total_byunits"]) { $usage = $usage_obj->data["total_byunits"]; } else { $usage = $usage_obj->data["total"]; } } unset($usage_obj); /* Charge for the usage in units */ $unitname = addslashes($obj_service->data["units"]); if ($usage > $obj_service->data["included_units"]) { // there is excess usage that we can bill for. $usage_excess = $usage - $obj_service->data["included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["price_extraunits"]; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = $unitname; // description example: Used 120 ZZ out of 50 ZZ included in plan // Excess usage of 70 ZZ charged at $5.00 per ZZ $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; $itemdata["description"] .= "\nExcess usage of {$usage_excess} {$unitname} charged at " . $obj_service->data["price_extraunits"] . " per {$unitname}."; } else { // description example: Used 120 ZZ out of 50 ZZ included in plan $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; } /* Add the item to the invoice */ $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); /* Update usage value for period - this summary value is visable on the service history page and saves having to query lots of records to generate period totals. */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='{$usage}' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); break; case "licenses": /* LICENSES No data usage, but there is a quantity field for the customer's account to specify the quantity of licenses that they have. */ log_write("debug", "service_invoicegen", "Processing usage items for licenses"); /* Usage Item Basics */ // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; /* Determine Additional Charges */ // charge for any extra licenses if ($obj_service->data["quantity"] > $obj_service->data["included_units"]) { // there is excess usage that we can bill for. $licenses_excess = $obj_service->data["quantity"] - $obj_service->data["included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["price_extraunits"] * $ratio; $itemdata["quantity"] = $licenses_excess; $itemdata["units"] = addslashes($obj_service->data["units"]); // description example: 10 licences included // 2 additional licenses charged at $24.00 each $itemdata["description"] .= "\n" . $obj_service->data["included_units"] . " " . $obj_service->data["units"] . " included"; $itemdata["description"] .= "\n{$licenses_excess} additional " . $obj_service->data["units"] . " charged at " . $obj_service->data["price_extraunits"] . " each."; } else { // description example: 10 licenses $itemdata["description"] .= "\n" . $period_data["quantity"] . " " . $period_data["units"] . "."; } /* Add the item to the invoice */ $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); break; case "time": /* TIME Simular to the generic usage type, but instead of units being a text field, units is an ID to the service_units table. */ log_write("debug", "service_invoicegen", "Processing usage items for time traffic"); /* Usage Item Basics */ // start the item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; /* Adjust Included Units to handle partial or extended periods */ if ($ratio != "1") { $obj_service->data["included_units"] = sprintf("%d", $obj_service->data["included_units"] * $ratio); } /* Fetch usage amount */ $usage_obj = new service_usage_generic(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; if ($usage_obj->load_data_service()) { $usage_obj->fetch_usagedata(); if ($usage_obj->data["total_byunits"]) { $usage = $usage_obj->data["total_byunits"]; } else { $usage = $usage_obj->data["total"]; } } unset($usage_obj); /* Charge for the usage in units */ $unitname = sql_get_singlevalue("SELECT name as value FROM service_units WHERE id='" . $obj_service->data["units"] . "'"); if ($usage > $obj_service->data["included_units"]) { // there is excess usage that we can bill for. $usage_excess = $usage - $obj_service->data["included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["price_extraunits"]; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = $unitname; // description example: Used 120 GB out of 50 GB included in plan // Excess usage of 70 GB charged at $5.00 per GB $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; $itemdata["description"] .= "\nExcess usage of {$usage_excess} {$unitname} charged at " . $obj_service->data["price_extraunits"] . " per {$unitname}."; } else { // description example: Used 10 out of 50 included units $itemdata["description"] .= "\nUsed {$usage} {$unitname} out of " . $obj_service->data["included_units"] . " {$unitname} included in plan."; } /* Add the item to the invoice */ $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); /* Update usage value for period - this summary value is visable on the service history page and saves having to query lots of records to generate period totals. */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='{$usage}' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); break; case "data_traffic": /* DATA_TRAFFIC We make use of the service_usage_traffic logic to determine the usage across all IP addressess assigned to this customer and then bill accordingly. */ log_write("debug", "service_invoicegen", "Processing usage items for time/data_traffic"); /* Fetch data traffic plan usage type */ $unitname = sql_get_singlevalue("SELECT name as value FROM service_units WHERE id='" . $obj_service->data["units"] . "'"); /* Fetch usage amount - the returned usage structure will include breakdown of traffic by configured types. */ $usage_obj = new service_usage_traffic(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; /* 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 = $obj_service->id; $traffic_types_obj->id_service_customer = $period_usage_data["id_service_customer"]; $traffic_types_obj->load_data_traffic_caps(); $traffic_types_obj->load_data_override_caps(); /* Generate Traffic Bills */ if ($usage_obj->load_data_service()) { $usage_obj->fetch_usage_traffic(); foreach ($traffic_types_obj->data as $data_traffic_cap) { // Adjust Included Units to handle partial or extended periods if ($ratio != "1") { $data_traffic_cap["cap_units_included"] = sprintf("%d", $data_traffic_cap["cap_units_included"] * $ratio); } // if there is only a single traffic cap, we should make the traffic type name blank, since there's only going to be // one line item anyway. if ($traffic_types_obj->data_num_rows == 1) { $data_traffic_cap["type_name"] = ""; } // if the any traffic type is zero and there are other traffic types, we should skip it, since most likely // the other traffic types provide everything expected. // if ($traffic_types_obj->data_num_rows > 1 && $data_traffic_cap["type_label"] == "*" && $usage_obj->data["total_byunits"]["*"] == 0) { continue; } // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; // base details $itemdata["price"] = 0; $itemdata["quantity"] = 0; $itemdata["discount"] = 0; $itemdata["units"] = ""; $itemdata["description"] = addslashes($obj_service->data["name_service"]) . " usage from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"]; if ($data_traffic_cap["cap_mode"] == "unlimited") { // unlimited data cap, there will never be any excess traffic charges, so the line item // description should be purely for informative purposes. $itemdata["description"] .= "\nUnlimited " . addslashes($data_traffic_cap["type_name"]) . " traffic, total of " . $usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] . " {$unitname} used\n"; } else { // capped traffic - check for excess changes, otherwise just report on how much traffic // that the customer used. // description example: Used 10 GB out of 50 GB included in plan $itemdata["description"] .= "\nCapped " . addslashes($data_traffic_cap["type_name"]) . " traffic, used " . $usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] . " {$unitname} out of " . $data_traffic_cap["cap_units_included"] . " {$unitname} in plan."; // handle excess charges // if ($usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] > $data_traffic_cap["cap_units_included"]) { // there is excess usage that we can bill for. $usage_excess = $usage_obj->data["total_byunits"][$data_traffic_cap["type_label"]] - $data_traffic_cap["cap_units_included"]; // set item attributes $itemdata["price"] = $data_traffic_cap["cap_units_price"]; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = $unitname; // description example: Excess usage of 70 GB charged at $5.00 per GB $itemdata["description"] .= "\nExcess usage of {$usage_excess} {$unitname} charged at " . format_money($data_traffic_cap["cap_units_price"]) . " per {$unitname}."; } } // end of traffic cap mode // add trunk usage item // $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } } /* Update usage value for period - this summary value is visable on the service history page and saves having to query lots of records to generate period totals. */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET usage_summary='" . $usage_obj->data["total_byunits"]["total"] . "' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); unset($usage_obj); unset($traffic_types_obj); break; case "phone_single": case "phone_tollfree": case "phone_trunk": /* PHONE_* SERVICES The phone services are special and contain multiple usage items, for: * Additional DDI numbers * Additional Trunks There are also multiple items for the call charges, grouped into one item for each DDI. */ log_write("debug", "service_invoicegen", "Processing usage items for phone_single/phone_tollfree/phone_trunk"); // setup usage object $usage_obj = new service_usage_cdr(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; $usage_obj->load_data_service(); /* 1. DDI CHARGES We need to fetch the total number of DDIs and see if there are any excess charges due to overage of the allocated amount in the plan. */ if ($obj_service->data["typeid_string"] == "phone_trunk" && $period_data["mode"] == "standard") { // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; // fetch DDI usage $usage = $usage_obj->load_data_ddi(); // determine excess usage charges if ($usage > $obj_service->data["phone_ddi_included_units"]) { // there is excess usage that we can bill for. $usage_excess = $usage - $obj_service->data["phone_ddi_included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["phone_ddi_price_extra_units"] * $ratio; log_write("debug", "DEBUG", "Ratio is {$ratio}, price is " . $itemdata["price"] . ""); $itemdata["quantity"] = $usage_excess; $itemdata["units"] = "DDIs"; if ($obj_service->data["phone_ddi_included_units"]) { $itemdata["description"] = $obj_service->data["phone_ddi_included_units"] . "x DDI numbers included in service plan plus additional " . $usage_excess . "x numbers from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } else { // no included units, we use an alternative string format $itemdata["description"] = $usage_excess . "x DDI numbers from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } } else { // no charge for this item $itemdata["description"] = $obj_service->data["phone_ddi_included_units"] . "x DDI numbers included in service plan from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } // add trunk usage item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } /* 2. Trunk Charges */ if (($obj_service->data["typeid_string"] == "phone_trunk" || $obj_service->data["typeid_string"] == "phone_tollfree") && $period_data["mode"] == "standard") { // fetch the number of trunks included in the plan, along with the number provided // we can then see if there are any excess charges for these // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; $itemdata["discount"] = 0; // determine excess usage charges if ($obj_service->data["phone_trunk_quantity"] > $obj_service->data["phone_trunk_included_units"]) { // there is excess usage that we can bill for. $usage_excess = $obj_service->data["phone_trunk_quantity"] - $obj_service->data["phone_trunk_included_units"]; // set item attributes $itemdata["price"] = $obj_service->data["phone_trunk_price_extra_units"] * $ratio; $itemdata["quantity"] = $usage_excess; $itemdata["units"] = "trunks"; if ($obj_service->data["phone_trunk_included_units"]) { $itemdata["description"] = $obj_service->data["phone_trunk_included_units"] . "x trunks included in service plan plus additional " . $usage_excess . "x trunks from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } else { // no included trunks, adjust string to suit. $itemdata["description"] = $usage_excess . "x trunks from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } } else { // no charge for this item $itemdata["description"] = $obj_service->data["phone_trunk_included_units"] . "x trunks included in service plan from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; } // add trunk usage item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } /* Call Charges Use CDR usage billing module to handle call charges. */ $usage_obj = new service_usage_cdr(); $usage_obj->id_service_customer = $period_usage_data["id_service_customer"]; $usage_obj->date_start = $period_usage_data["date_start"]; $usage_obj->date_end = $period_usage_data["date_end"]; $billgroup_obj = new sql_query(); $billgroup_obj->string = "SELECT id, billgroup_name FROM cdr_rate_billgroups"; $billgroup_obj->execute(); $billgroup_obj->fetch_array(); if ($usage_obj->load_data_service()) { $usage_obj->fetch_usage_calls(); foreach ($billgroup_obj->data as $data_billgroup) { foreach ($usage_obj->data_ddi as $ddi) { if ($usage_obj->data[$ddi][$data_billgroup["id"]]["charges"] > 0) { // start service item $invoice_item = new invoice_items(); $invoice_item->id_invoice = $invoiceid; $invoice_item->type_invoice = "ar"; $invoice_item->type_item = "service_usage"; $itemdata = array(); $itemdata["chartid"] = $obj_service->data["chartid"]; $itemdata["customid"] = $obj_service->id; // extra service details $itemdata["id_service_customer"] = $period_usage_data["id_service_customer"]; $itemdata["id_period"] = $period_usage_data["id"]; // determine excess usage charges $itemdata["discount"] = 0; $itemdata["price"] = $usage_obj->data[$ddi][$data_billgroup["id"]]["charges"]; $itemdata["quantity"] = "1"; $itemdata["units"] = ""; $itemdata["description"] = $data_billgroup["billgroup_name"] . " call charges for {$ddi} from " . $period_usage_data["date_start"] . " to " . $period_usage_data["date_end"] . ""; $itemdata["cdr_billgroup"] = $data_billgroup["id"]; // add trunk usage item $invoice_item->prepare_data($itemdata); $invoice_item->action_update(); unset($invoice_item); } else { log_write("debug", "inc_service_invoicegen", "Excluding DDI {$ddi} from " . $data_billgroup["billgroup_name"] . " due to no charges for the current period"); } } } } unset($usage_obj); /* If enabled, generate the CDR output format for the current service usage and attach it to the invoice via the invoice journal. This feature can be enabled/disabled on a per-customer per-service basis. */ if ($obj_service->data["billing_cdr_csv_output"]) { log_write("debug", "inc_service_invoicegen", "Generating CDR export file and attaching to invoice journal"); // generate the CSV formatted export. $cdr_options = array('id_customer' => $customer_data["id"], 'id_service_customer' => $period_usage_data["id_service_customer"], 'period_start' => $period_usage_data["date_start"], 'period_end' => $period_usage_data["date_end"]); $csv = new cdr_csv($cdr_options); if (!($cdr_output = $csv->getCSV())) { log_write("error", "inc_service_invoicegen", "Unable to generate CSV ouput for the configured range"); return 0; } // create journal entry $journal = new journal_process(); $journal->prepare_set_journalname("account_ar"); $journal->prepare_set_customid($invoiceid); $journal->prepare_set_type("file"); // we use the prefix "SERVICE:" to find the journal at invoice time $journal->prepare_set_title("SERVICE: Service CDR Export Attachment"); // details can be anything (just a text block) $data["content"] = NULL; $data["content"] .= "Automatically exported CDR for service " . addslashes($obj_service->data["name_service"]) . "\n"; $data["content"] .= "\n"; $journal->prepare_set_content($data["content"]); $journal->action_update(); // create journal entry $journal->action_lock(); // lock entry to avoid users deleting it or breaking it // upload file as an attachment for the journal $file_obj = new file_storage(); $file_obj->data["type"] = "journal"; $file_obj->data["customid"] = $journal->structure["id"]; $file_obj->data["file_name"] = "invoice_" . $invoicecode . "_service_CDR_export.csv"; if (!$file_obj->action_update_var($cdr_output)) { log_write("error", "inc_service_invoicegen", "Unable to upload export CDR invoice to journal."); } unset($csv); unset($journal); unset($file_obj); unset($cdr_output); } break; case "generic_no_usage": case "bundle": // nothing todo for these service types log_write("debug", "service_invoicegen", "Not processing usage, this is a non-usage service type"); break; default: // we should always match all service types, even if we don't need to do anything // in particular for that type. die("Unable to process unknown service type: " . $obj_service->data["typeid_string"] . ""); break; } // end of processing usage } else { log_write("debug", "service_invoicegen", "Not billing for current usage, as this appears to be the first plan period so no usage can exist yet"); } /* Set invoice ID for period - this prevents the period from being added to any other invoices and allows users to see which invoice it was billed under */ // set for plan period $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET invoiceid='{$invoiceid}', rebill='0' WHERE id='" . $period_data["id"] . "' LIMIT 1"; $sql_obj->execute(); // set for usage period if (!empty($period_usage_data["active"])) { $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET invoiceid_usage='{$invoiceid}' WHERE id='" . $period_usage_data["id"] . "' LIMIT 1"; $sql_obj->execute(); } } // end of processing periods /* Only process orders and invoice summary details if we had no errors above. */ if (!error_check()) { /* Process any customer orders */ if ($GLOBALS["config"]["ORDERS_BILL_ONSERVICE"]) { log_write("debug", "inc_service_invoicegen", "Checking for customer orders to add to service invoice"); $obj_customer_orders = new customer_orders(); $obj_customer_orders->id = $customer_data["id"]; if ($obj_customer_orders->check_orders_num()) { log_write("debug", "inc_service_invoicegen", "Order items exist, adding them to service invoice"); $obj_customer_orders->invoice_generate($invoiceid); } } else { log_write("debug", "inc_service_invoicegen", "Not checking for customer orders, ORDERS_BILL_ONSERVICE is disabled currently"); } /* Update the invoice details + Ledger Processes: - taxes - ledger - invoice summary We use the invoice_items class to perform these tasks, but we don't need to define an item ID for the functions being used to work. */ $invoice = new invoice_items(); $invoice->id_invoice = $invoiceid; $invoice->type_invoice = "ar"; $invoice->action_update_tax(); $invoice->action_update_ledger(); $invoice->action_update_total(); unset($invoice); /* Update period information with invoiceid */ $sql_obj = new sql_query(); $sql_obj->string = "UPDATE services_customers_periods SET invoiceid='{$invoiceid}' WHERE id='" . $period_data["id"] . "'"; $sql_obj->execute(); } // end if error check /* Automatic Payments Makes automatic invoice payments using sources such as customer credit pools, reoccuring credit card transactions and other sources. */ if ($GLOBALS["config"]["ACCOUNTS_AUTOPAY"]) { log_write("debug", "inc_services_invoicegen", "Autopay Functionality Enabled, running appropiate functions for invoice ID {$invoiceid}"); $obj_autopay = new invoice_autopay(); $obj_autopay->id_invoice = $invoiceid; $obj_autopay->type_invoice = "ar"; $obj_autopay->autopay(); unset($obj_autopay); } /* Commit Conduct final error check, before commiting the new invoice and sending the customer an email if appropiate. (we obviously don't want to email them if the invoice is getting rolled back!) */ $sql_obj = new sql_query(); if (error_check()) { $sql_obj->trans_rollback(); log_write("error", "inc_services_invoicegen", "An error occured whilst creating service invoice. No changes have been made."); } else { $sql_obj->trans_commit(); // invoice creation complete - remove any notifications made by the invoice functions and return // our own notification $_SESSION["notification"]["message"] = array(); log_write("notification", "inc_services_invoicegen", "New invoice {$invoicecode} for customer " . $customer_data["code_customer"] . " created"); /* Send the invoice to the customer as a PDF via email */ $emailed = "unsent"; if (sql_get_singlevalue("SELECT value FROM config WHERE name='EMAIL_ENABLE'") == "enabled") { if (sql_get_singlevalue("SELECT value FROM config WHERE name='ACCOUNTS_INVOICE_AUTOEMAIL'") == "enabled") { // load completed invoice data $invoice = new invoice(); $invoice->id = $invoiceid; $invoice->type = "ar"; $invoice->load_data(); $invoice->load_data_export(); if ($invoice->data["amount_total"] > 0) { // generate an email $email = $invoice->generate_email(); // send email $invoice->email_invoice("system", $email["to"], $email["cc"], $email["bcc"], $email["subject"], $email["message"]); // complete log_write("notification", "inc_services_invoicegen", "Invoice {$invoicecode} has been emailed to customer (" . $email["to"] . ")"); } else { // complete - invoice is for $0, so don't want to email out log_write("notification", "inc_services_invoicegen", "Invoice {$invoicecode} has not been emailed to the customer due to invoice being for \$0."); } $emailed = "emailed - " . $email["to"]; unset($invoice); } } // end if email enabled } // end if commit successful /* Review Status Here we need to check whether invoicing succeded/failed for the selected customer and process accordingly - we also want to re-set the error flag if running a batch mode, to enable other customers to still be invoiced. */ if (error_check()) { // an error occured $invoice_stats["total_failed"]++; $invoice_stats["failed"][] = array("code_customer" => $customer_data["code_customer"], "name_customer" => $customer_data["name_customer"], "code_invoice" => $invoicecode); // clear the error strings if we are processing from CLI this will allow us to continue on // with additional invoices. if (!empty($_SESSION["mode"])) { if ($_SESSION["mode"] == "cli") { log_write("debug", "inc_services_invoicegen", "Processing from CLI, clearing error flag and continuing with additional invoices"); error_clear(); } } } else { // successful $invoice_stats["total"]++; $invoice_stats["generated"][] = array("code_customer" => $customer_data["code_customer"], "name_customer" => $customer_data["name_customer"], "code_invoice" => $invoicecode, "sent" => $emailed); } // end of success/stats review } // end of processing customers } // end of if customers exist } else { log_debug("inc_services_invoicegen", "No services assigned to customer {$customerid}"); } /* Write Invoicing Report This only takes place if no customer ID is provided, eg we have run a full automatic invoice generation report. */ if ($customerid == NULL) { log_write("debug", "inc_service_invoicegen", "Generating invoice report for invoice generation process"); /* Invoice Stats Calculations */ $invoice_stats["time"] = time() - $invoice_stats["time_start"]; if ($invoice_stats["time"] == 0) { $invoice_stats["time"] = 1; } /* Write Invoice Report */ $invoice_report = array(); $invoice_report[] = "Complete Invoicing Run Time:\t" . $invoice_stats["time"] . " seconds"; $invoice_report[] = "Total Invoices Generated:\t" . $invoice_stats["total"]; $invoice_report[] = "Failed Invoices/Customers:\t" . $invoice_stats["total_failed"]; if (isset($invoice_stats["generated"])) { $invoice_report[] = ""; $invoice_report[] = "Customers / Invoices Generated"; foreach (array_keys($invoice_stats["generated"]) as $id) { $invoice_report[] = " * " . $invoice_stats["generated"][$id]["code_invoice"] . ": " . $invoice_stats["generated"][$id]["code_customer"] . " -- " . $invoice_stats["generated"][$id]["name_customer"]; $invoice_report[] = " [" . $invoice_stats["generated"][$id]["sent"] . "]"; } $invoice_report[] = ""; } if (isset($invoice_stats["failed"])) { $invoice_report[] = ""; $invoice_report[] = "Failed Customers / Invoices"; foreach (array_keys($invoice_stats["failed"]) as $id) { $invoice_report[] = " * " . $invoice_stats["failed"][$id]["code_customer"] . " -- " . $invoice_stats["failed"][$id]["name_customer"]; } $invoice_report[] = ""; $invoice_report[] = "Failed invoices will be attempted again on the following billing run."; $invoice_report[] = ""; } $invoice_report[] = "Invoicing Run Complete"; // display to debug log log_write("debug", "inc_service_invoicegen", "----"); foreach ($invoice_report as $line) { // loop through invoice report lines log_write("debug", "inc_service_invoicegen", $line); } log_write("debug", "inc_service_invoicegen", "----"); // email if appropiate if ($GLOBALS["config"]["ACCOUNTS_INVOICE_BATCHREPORT"] && ($invoice_stats["total"] > 0 || $invoice_stats["total_failed"] > 0)) { log_write("debug", "inc_service_invoicegen", "Emailing invoice generation report to " . $GLOBALS["config"]["ACCOUNTS_EMAIL_ADDRESS"] . ""); /* External dependency of Mail_Mime */ if (!@(include_once 'Mail.php')) { log_write("error", "invoice", "Unable to find Mail module required for sending email"); break; } if (!@(include_once 'Mail/mime.php')) { log_write("error", "invoice", "Unable to find Mail::Mime module required for sending email"); break; } /* Email the Report */ $email_sender = $GLOBALS["config"]["ACCOUNTS_EMAIL_ADDRESS"]; $email_to = $GLOBALS["config"]["ACCOUNTS_EMAIL_ADDRESS"]; $email_subject = "Invoice Batch Process Report"; $email_message = $GLOBALS["config"]["COMPANY_NAME"] . "\n\nInvoice Batch Process Report for " . time_format_humandate() . "\n\n"; foreach ($invoice_report as $line) { $email_message .= $line . "\n"; } // prepare headers $mail_headers = array('From' => $email_sender, 'Subject' => $email_subject); $mail_mime = new Mail_mime("\n"); $mail_mime->setTXTBody($email_message); $mail_body = $mail_mime->get(); $mail_headers = $mail_mime->headers($mail_headers); $mail =& Mail::factory('mail'); $status = $mail->send($email_to, $mail_headers, $mail_body); if (PEAR::isError($status)) { log_write("error", "inc_service_invoicegen", "An error occured whilst attempting to send the batch report email: " . $status->getMessage() . ""); } else { log_write("debug", "inc_service_invoicegen", "Successfully sent batch report email."); } } // end if email } // end if report return 1; }