/** * Calculates the number of days between first and second parameter * * @param int Date 1 * @param int Date 2 * @return int Number of days * * @author Former03 GmbH :: Florian Lippert <*****@*****.**> */ function calculateDayDifference($begin, $end) { $daycount = 0; $begin = transferDateToArray($begin); $end = transferDateToArray($end); $direction = 1; // Sanity check, if our given array is in the right format if (checkDateArray($begin) === true && checkDateArray($end) === true) { if (strtotime($end['y'] . '-' . $end['m'] . '-' . $end['d']) < strtotime($begin['y'] . '-' . $begin['m'] . '-' . $begin['d'])) { $tmp = $end; $end = $begin; $begin = $tmp; unset($tmp); $direction = -1; } $yeardiff = (int) $end['y'] - (int) $begin['y']; $monthdiff = (int) $end['m'] + 12 * $yeardiff - (int) $begin['m']; for ($i = 0; $i < abs($monthdiff); $i++) { $daycount += getDaysForMonth($begin['m'] + $i, $begin['y']); } $daycount += $end['d'] - $begin['d']; $daycount *= $direction; } return $daycount; }
/** * This method is a wrapper for parent::collect. Before launching it, * we will gather information about traffic usage for all given userids. * * @param bool Should we fix invoice (means we call self::setLastInvoiced to latest invoiced date). * @param bool Should we include the setup fee? * @param bool Should we include the interval fees? * @return array All invoice rows * * @author Former03 GmbH :: Florian Lippert <*****@*****.**> */ function collect($fixInvoice = false, $include_setup_fee = false, $include_interval_fee = false) { $traffic_result = $this->db->query('SELECT `' . getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key') . '`, `year`, `month`, `day`, SUM(`http`+`ftp_down`+`ftp_up`+`mail`) as traffic FROM `' . getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'table') . '` WHERE `' . getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key') . '` IN ( ' . implode(', ', $this->userIds) . ' ) GROUP BY `' . getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key') . '`, `year`, `month`, `day`'); while ($traffic_row = $this->db->fetch_array($traffic_result)) { if (!isset($this->traffic_data[$traffic_row[getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key')]]) || !is_array($this->traffic_data[$traffic_row[getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key')]])) { $this->traffic_data[$traffic_row[getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key')]] = array(); } $date = $traffic_row['year'] . '-' . $traffic_row['month'] . '-' . $traffic_row['day']; if (!isset($this->traffic_data[$traffic_row[getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key')]][$date]) && checkDateArray(transferDateToArray($date))) { $this->traffic_data[$traffic_row[getModeDetails($this->mode, 'TABLE_PANEL_TRAFFIC', 'key')]][$date] = (int) $traffic_row['traffic']; } } reset($this->userIds); foreach ($this->userIds as $userId) { // Using fixed values here, because those settings are always the same. interval_fee will be calculated lateron, when traffic consumption is calculated $this->service_details[$userId]['service_active'] = '1'; $this->service_details[$userId]['interval_fee'] = '0.00'; $this->service_details[$userId]['interval_length'] = '1'; $this->service_details[$userId]['interval_type'] = 'm'; $this->service_details[$userId]['interval_payment'] = '1'; // Always postpaid, we can't invoice this month/payment_term's traffic, if it hasn't finished yet $this->service_details[$userId]['setup_fee'] = '0.00'; $this->service_details[$userId]['payment_every'] = '1'; $this->service_details[$userId]['lastinvoiced_date'] = $this->service_details[$userId]['lastinvoiced_date_traffic']; // We still want to be able to calculate traffic usage in case of no service information if (($this->service_details[$userId]['lastinvoiced_date'] == '0' || $this->service_details[$userId]['lastinvoiced_date'] == '') && ($this->service_details[$userId]['servicestart_date'] == '0' || $this->service_details[$userId]['servicestart_date'] == '') && isset($this->traffic_data[$userId]) && is_array($this->traffic_data[$userId]) && !empty($this->traffic_data[$userId])) { // Get the date of first appereance of traffic ksort($this->traffic_data[$userId]); $dates = array_keys($this->traffic_data[$userId]); $this->service_details[$userId]['servicestart_date'] = $dates[0]; } } return parent::collect($fixInvoice, $include_setup_fee, $include_interval_fee); }
/** * Manipulates a date, like adding a month or so and correcting it afterwards * (2008-01-33 -> 2008-02-02) * * @param array The date array * @param string The operation, may be '+', 'add', 'sum' or '-', 'subtract', 'subduct' * @param int Number if days/month/years * @param string Either 'y', 'm', 'd', depending on what part to change. * @param array A valid date array with original date, mandatory for more than one manipulation on same date. * @return date The manipulated date array * * @author Former03 GmbH :: Florian Lippert <*****@*****.**> */ function manipulateDate($date, $operation, $count, $type, $original_date = null) { $newdate = $date; $date = transferDateToArray($date); if (checkDateArray($date) === true && isset($date[$type])) { switch ($operation) { case '+': case 'add': case 'sum': $date[$type] += (int) $count; break; case '-': case 'subtract': case 'subduct': $date[$type] -= (int) $count; break; } if ($original_date !== null && ($original_date = transferDateToArray($original_date)) !== false && $type == 'm') { if ($original_date['d'] > getDaysForMonth($date['m'], $date['y'])) { $date['d'] = getDaysForMonth($date['m'], $date['y']) - (getDaysForMonth($original_date['m'], $original_date['y']) - $original_date['d']); } else { $date['d'] = $original_date['d']; } } while (checkDateArray($date) === false) { if ($date['d'] > getDaysForMonth($date['m'], $date['y'])) { $date['d'] -= getDaysForMonth($date['m'], $date['y']); $date['m']++; } if ($date['d'] < 1) { $date['m']--; $date['d'] += getDaysForMonth($date['m'], $date['y']); // Adding here, because date[d] is negative } if ($date['m'] > 12) { $date['m'] -= 12; $date['y']++; } if ($date['m'] < 1) { $date['y']--; $date['m'] += 12; } } $newdate = $date['y'] . '-' . $date['m'] . '-' . $date['d']; } return $newdate; }
/** * This method adds taxrates to the invoice rows. * * @param array The array containing all invoice rows * * @return array The array containing (taxed) invoice rows * * @author Former03 GmbH :: Florian Lippert <*****@*****.**> */ public function applyTaxRate($invoice) { $invoice_new = array(); foreach ($invoice as $rowid => $invoice_row) { // If we don't have a valid taxclass, use the default one. if (!isset($invoice_row['taxclass']) || !isset($this->taxclasses[$invoice_row['taxclass']])) { $invoice_row['taxclass'] = $this->default_taxclass; } if (isset($invoice_row['taxclass']) && isset($this->taxclasses[$invoice_row['taxclass']])) { // Once-fees are quite easy, just get the valid taxrate and add it to the row. if (isset($invoice_row['service_occurence']) && $invoice_row['service_occurence'] == 'once') { $taxchanges = $this->getTaxChanges($invoice_row['taxclass'], $invoice_row['service_date']); $pricing = array('taxrate' => $taxchanges[0]['taxrate'], 'total_fee' => $invoice_row['setup_fee']); $invoice_new[] = array_merge($invoice_row, $pricing); } elseif (isset($invoice_row['service_occurence']) && $invoice_row['service_occurence'] == 'period') { // Get all tax changes in our service interval $taxchanges = $this->getTaxChanges($invoice_row['taxclass'], $invoice_row['service_date_begin'], manipulateDate($invoice_row['service_date_end'], '-', 1, 'd')); // In pricing we store all changes to our invoice row, will get merged lateron. $pricing = array('taxrate' => $taxchanges[0]['taxrate'], 'total_fee' => 0); // number_days will store the days we already processed... $number_days = 0; // ... whereas days_diff contains the whole number of days of our service interval. $days_diff = calculateDayDifference($invoice_row['service_date_begin'], $invoice_row['service_date_end']); $service_date_begin_array = transferDateToArray($invoice_row['service_date_begin']); // Now we walk through the interval, stepping is the interval length. while ($days_diff >= $number_days + $this->getDaysForInterval($invoice_row['interval_length'], $invoice_row['interval_type'], $service_date_begin_array)) { // Whenever we happen to meet a tax change, remaining will reduced by the number of days to that tax change. $remaining = $interval_days = $this->getDaysForInterval($invoice_row['interval_length'], $invoice_row['interval_type'], $service_date_begin_array); $interval_begin = manipulateDate($invoice_row['service_date_begin'], '+', $number_days, 'd'); $interval_end = manipulateDate($invoice_row['service_date_begin'], '+', $number_days + $interval_days, 'd'); // Now get tax changes in the current interval. $taxchanges = $this->getTaxChanges($invoice_row['taxclass'], $interval_begin, $interval_end); // Maybe taxrate already changed on the first day of our interval. if ($pricing['taxrate'] != $taxchanges[0]['taxrate']) { $pricing['service_date_end'] = $taxchanges[0]['valid_to']; $invoice_new[] = array_merge($invoice_row, $pricing); $pricing['taxrate'] = $taxchanges[0]['taxrate']; $pricing['total_fee'] = 0; } // Anyways, we don't need the current taxrate. unset($taxchanges[0]); // Walk through all taxchanges... foreach ($taxchanges as $valid_from => $taxchange) { $tax_days = calculateDayDifference($interval_begin, $valid_from); // Subtract the days we are going to tax from the remaining days in our interval $remaining -= $tax_days; // total_fee is a fraction of the interval fee $pricing['total_fee'] += $invoice_row['interval_fee'] * ($tax_days / $interval_days); // Set ending day of row to day when tax changed $pricing['service_date_end'] = $taxchange['valid_from']; // And add a new row to invoice $invoice_new[] = array_merge($invoice_row, $pricing); // Next line begins with the day when tax changed $interval_begin = $pricing['service_date_begin'] = $taxchange['valid_from']; $pricing['taxrate'] = $taxchange['taxrate']; $pricing['total_fee'] = 0; } // Incruse number_days (loop condition value) $number_days += $interval_days; // also update service_date_begin_array, so self::getDaysForInterval returns a correct value in our loop condition $service_date_begin_array[$invoice_row['interval_type']] += $invoice_row['interval_length']; // Finally add the remaining fraction to total_fee $pricing['total_fee'] += $invoice_row['interval_fee'] * ($remaining / $interval_days); } // Last element, so use our real service_date_end unset($pricing['service_date_end']); // If there are still have some days left (e.g. when service was terminated during an interval)... if ($days_diff > $number_days) { // ... calculate last total_fee... $pricing['total_fee'] += $invoice_row['interval_fee'] * (($days_diff - $number_days) / $this->getDaysForInterval($invoice_row['interval_length'], $invoice_row['interval_type'], $service_date_begin_array)); } // ... and finally add last line. $invoice_new[] = array_merge($invoice_row, $pricing); } } else { $invoice_new[] = $invoice_row; } } return $invoice_new; }
/** * This method collects the invoice rows. * * @param bool Should we fix invoice (means we call self::setLastInvoiced to latest invoiced date). * @param bool Should we include the setup fee? * @param bool Should we include the interval fees? * * @return array All invoice rows * * @author Former03 GmbH :: Florian Lippert <*****@*****.**> */ public function collect($fixInvoice = false, $include_setup_fee = false, $include_interval_fee = false) { $invoice = array(); reset($this->service_details); foreach ($this->service_details as $serviceId => $service_detail) { if (checkDateArray(transferDateToArray($service_detail['servicestart_date'])) === true) { // Load template which is valid through our setup date $template = $this->findValidTemplate($service_detail['servicestart_date'], $this->selectAppropriateTemplateKey($service_detail)); foreach ($this->defaultvalues as $field => $value) { // We are using $this->service_details[$serviceId] instead of $service_detail so we can see the original values, as the "working copy" ($service_detail) could have been changed... if ((!isset($this->service_details[$serviceId][$field]) || isset($this->service_details[$serviceId][$field]) && $this->service_details[$serviceId][$field] == $value) && isset($template[$field]) && $template[$field] != $value) { $service_detail[$field] = $template[$field]; } } // If quantity is not set, we do need a value 1, otherwise this doesn't make sense... if (!isset($service_detail['quantity'])) { $service_detail['quantity'] = 1; } // Add setup fee to invoice if (checkDateArray(transferDateToArray($service_detail['lastinvoiced_date'])) !== true || $this->allowLastInvoicedDatePastServiceStart === false && calculateDayDifference($service_detail['lastinvoiced_date'], $service_detail['servicestart_date']) > 0) { if ($include_setup_fee === true) { $invoice[] = $this->buildInvoiceRowSetupFee($service_detail, $this->getServiceDescription($service_detail, 'setup')); } $service_detail['lastinvoiced_date'] = $service_detail['servicestart_date']; } // If payment_every is not set, we do need a value 1, otherwise nextinvoiced_date wouldn't be calculated correctly and we'll get stuck in an infinite loop if (!isset($service_detail['payment_every'])) { $service_detail['payment_every'] = 1; } if ((int) $service_detail['interval_length'] != 0 && (int) $service_detail['payment_every'] != 0 && in_array($service_detail['interval_type'], getIntervalTypes('array'))) { $original_date = $service_detail['lastinvoiced_date']; $service_detail['nextinvoiced_date'] = manipulateDate($service_detail['lastinvoiced_date'], '+', (int) $service_detail['interval_length'] * (int) $service_detail['payment_every'], $service_detail['interval_type'], $original_date); while ($service_detail['interval_payment'] == CONST_BILLING_INTERVALPAYMENT_PREPAID && calculateDayDifference($service_detail['lastinvoiced_date'], time()) >= 0 && !($service_detail['service_active'] == '0' && calculateDayDifference($service_detail['lastinvoiced_date'], $service_detail['serviceend_date']) <= 0) || $service_detail['interval_payment'] == CONST_BILLING_INTERVALPAYMENT_POSTPAID && (calculateDayDifference($service_detail['nextinvoiced_date'], time()) >= 0 || $this->endServiceImmediately === true && $service_detail['service_active'] == '0' && calculateDayDifference($service_detail['lastinvoiced_date'], $service_detail['serviceend_date']) > 0 && calculateDayDifference($service_detail['serviceend_date'], $service_detail['nextinvoiced_date']) >= 0 && calculateDayDifference($service_detail['lastinvoiced_date'], time()) >= 0 && calculateDayDifference($service_detail['serviceend_date'], time()) >= 0)) { // Reload template which is valid through our current invoice period reset($this->defaultvalues); $template = $this->findValidTemplate($service_detail['lastinvoiced_date'], $this->selectAppropriateTemplateKey($service_detail)); foreach ($this->defaultvalues as $field => $value) { // We are using $this->service_details[$serviceId] instead of $service_detail so we can see the original values, as the "working copy" ($service_detail) could have been changed... if ((!isset($this->service_details[$serviceId][$field]) || isset($this->service_details[$serviceId][$field]) && $this->service_details[$serviceId][$field] == $value) && isset($template[$field]) && $template[$field] != $value) { $service_detail[$field] = $template[$field]; } } if ($this->endServiceImmediately === true && $service_detail['service_active'] == '0' && calculateDayDifference($service_detail['lastinvoiced_date'], $service_detail['serviceend_date']) > 0 && calculateDayDifference($service_detail['serviceend_date'], $service_detail['nextinvoiced_date']) >= 0 && calculateDayDifference($service_detail['lastinvoiced_date'], time()) >= 0 && calculateDayDifference($service_detail['serviceend_date'], time()) >= 0) { $service_detail['nextinvoiced_date'] = $service_detail['serviceend_date']; } // Sanity check, shouldn't be needed... if (calculateDayDifference($service_detail['lastinvoiced_date'], $service_detail['nextinvoiced_date']) >= 0) { $service_detail['service_date_begin'] = $service_detail['lastinvoiced_date']; $service_detail['service_date_end'] = $service_detail['nextinvoiced_date']; if ($include_interval_fee === true) { $invoice[] = $this->buildInvoiceRowIntervalFee($service_detail, $this->getServiceDescription($service_detail, 'interval')); } } // Go on in loop, set lastinvoiced_date to nextinvoiced_date ... $service_detail['lastinvoiced_date'] = $service_detail['nextinvoiced_date']; // ... and recalculate nextinvoiced_date. $service_detail['nextinvoiced_date'] = manipulateDate($service_detail['lastinvoiced_date'], '+', (int) $service_detail['interval_length'] * (int) $service_detail['payment_every'], $service_detail['interval_type'], $original_date); } } if ($fixInvoice === true) { $this->setLastInvoiced($serviceId, $service_detail); } } } return $invoice; }