// Class ttInvoiceHelper is used for help with invoices.
class ttInvoiceHelper {
-
+
// insert - inserts an invoice in database.
static function insert($fields)
{
$mdb2 = getConnection();
-
+
$team_id = (int) $fields['team_id'];
$name = $fields['name'];
- if (!$name) return false;
- $client_id = (int) $fields['client_id'];
- $date = $fields['date'];
+ if (!$name) return false;
+
+ $client_id = (int) $fields['client_id'];
+ $date = $fields['date'];
if (array_key_exists('status', $fields)) { // Key exists and may be NULL during migration of data.
$status_f = ', status';
$status_v = ', '.$mdb2->quote($fields['status']);
}
-
+
// Insert a new invoice record.
$sql = "insert into tt_invoices (team_id, name, date, client_id $status_f)
values($team_id, ".$mdb2->quote($name).", ".$mdb2->quote($date).", $client_id $status_v)";
$affected = $mdb2->exec($sql);
-
- if (is_a($affected, 'PEAR_Error'))
- return false;
+
+ if (is_a($affected, 'PEAR_Error')) return false;
$last_id = 0;
$sql = "select last_insert_id() as last_insert_id";
$res = $mdb2->query($sql);
$val = $res->fetchRow();
$last_id = $val['last_insert_id'];
-
- return $last_id;
+
+ return $last_id;
}
-
+
// getInvoice - obtains invoice data from the database.
static function getInvoice($invoice_id) {
- global $user;
+ global $user;
$mdb2 = getConnection();
-
+
$sql = "select * from tt_invoices where id = $invoice_id and team_id = $user->team_id and status = 1";
$res = $mdb2->query($sql);
if (!is_a($res, 'PEAR_Error')) {
}
return false;
}
-
+
// The getInvoiceByName looks up an invoice by name.
static function getInvoiceByName($invoice_name) {
-
+
$mdb2 = getConnection();
global $user;
$sql = "select id from tt_invoices where team_id = $user->team_id and name = ".$mdb2->quote($invoice_name)." and status = 1";
-
- $res = $mdb2->query($sql);
- if (!is_a($res, 'PEAR_Error')) {
+
+ $res = $mdb2->query($sql);
+ if (!is_a($res, 'PEAR_Error')) {
$val = $res->fetchRow();
- if ($val['id']) {
+ if ($val['id']) {
return $val;
}
}
// At this time only detailed invoice is supported.
// It is anticipated to support "totals only" option later on.
-
+
// Our query is different depending on tracking mode.
if (MODE_TIME == $user->tracking_mode) {
// In "time only" tracking mode there is a single user rate.
left join tt_user_project_binds upb on (upb.user_id = l.user_id and upb.project_id = l.project_id)
where l.status = 1 and l.billable = 1 and l.invoice_id = $invoice_id order by l.date, u.name";
}
-
+
// If we have expenses, we need to do a union with a separate query for expense items from tt_expense_items table.
if (in_array('ex', explode(',', $user->plugins))) { // if ex(penses) plugin is enabled
$sql_for_expense_items = "select ei.date as date, 2 as type, u.name as user_name, p.name as project_name,
inner join tt_users u on (ei.user_id = u.id)
left join tt_projects p on (p.id = ei.project_id)
where ei.invoice_id = $invoice_id and ei.status = 1";
-
+
// Construct a union.
$sql = "($sql) union all ($sql_for_expense_items)";
-
+
$sort_part = " order by date, user_name, type";
$sql .= $sort_part;
}
-
+
$res = $mdb2->query($sql);
if (!is_a($res, 'PEAR_Error')) {
$dt = new DateAndTime(DB_DATEFORMAT);
}
return $result;
}
-
+
// delete - deletes the invoice data from the database.
static function delete($invoice_id, $delete_invoice_items) {
- global $user;
+ global $user;
$mdb2 = getConnection();
// Handle custom field log records.
if ($delete_invoice_items) {
$sql = "update tt_custom_field_log set status = NULL where log_id in (select id from tt_log where invoice_id = $invoice_id and status = 1)";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
+ if (is_a($affected, 'PEAR_Error')) return false;
}
-
+
// Handle time records.
if ($delete_invoice_items)
$sql = "update tt_log set status = NULL where invoice_id = $invoice_id";
else
$sql = "update tt_log set invoice_id = NULL where invoice_id = $invoice_id";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
+ if (is_a($affected, 'PEAR_Error')) return false;
// Handle expense items.
- if ($delete_invoice_items)
+ if ($delete_invoice_items)
$sql = "update tt_expense_items set status = NULL where invoice_id = $invoice_id";
- else
+ else
$sql = "update tt_expense_items set invoice_id = NULL where invoice_id = $invoice_id";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
-
- $sql = "update tt_invoices set status = NULL where id = $invoice_id and team_id = $user->team_id";
- $affected = $mdb2->exec($sql);
+ if (is_a($affected, 'PEAR_Error')) return false;
+
+ $sql = "update tt_invoices set status = NULL where id = $invoice_id and team_id = $user->team_id";
+ $affected = $mdb2->exec($sql);
return (!is_a($affected, 'PEAR_Error'));
}
// The invoiceableItemsExist determines whether invoiceable records exist in the specified period.
static function invoiceableItemsExist($fields) {
-
- $mdb2 = getConnection();
+
+ $mdb2 = getConnection();
global $user;
$client_id = (int) $fields['client_id'];
-
+
$start_date = new DateAndTime($user->date_format, $fields['start_date']);
$start = $start_date->toString(DB_DATEFORMAT);
-
+
$end_date = new DateAndTime($user->date_format, $fields['end_date']);
$end = $end_date->toString(DB_DATEFORMAT);
-
+
if (isset($fields['project_id'])) $project_id = (int) $fields['project_id'];
-
+
// Our query is different depending on tracking mode.
if (MODE_TIME == $user->tracking_mode) {
// In "time only" tracking mode there is a single user rate.
} else {
// sql part for project id.
if ($project_id) $project_part = " and l.project_id = $project_id";
-
+
// When we have projects, rates are defined for each project in tt_user_project_binds table.
$sql = "select count(*) as num from tt_log l, tt_user_project_binds upb
where l.status = 1 and l.client_id = $client_id $project_part and l.invoice_id is NULL
and l.billable * upb.rate * time_to_sec(l.duration)/3600 > 0
and upb.user_id = l.user_id and upb.project_id = l.project_id";
}
- $res = $mdb2->query($sql);
- if (!is_a($res, 'PEAR_Error')) {
+ $res = $mdb2->query($sql);
+ if (!is_a($res, 'PEAR_Error')) {
$val = $res->fetchRow();
- if ($val['num']) {
+ if ($val['num']) {
return true;
}
}
-
+
// sql part for project id.
if ($project_id) $project_part = " and ei.project_id = $project_id";
-
+
$sql = "select count(*) as num from tt_expense_items ei
where ei.client_id = $client_id $project_part and ei.invoice_id is NULL
and ei.date >= ".$mdb2->quote($start)." and ei.date <= ".$mdb2->quote($end)."
and ei.cost <> 0 and ei.status = 1";
$res = $mdb2->query($sql);
- if (!is_a($res, 'PEAR_Error')) {
+ if (!is_a($res, 'PEAR_Error')) {
$val = $res->fetchRow();
- if ($val['num']) {
+ if ($val['num']) {
return true;
}
}
-
+
return false;
}
-
+
// createInvoice - marks items for invoice as belonging to it (with its reference number).
static function createInvoice($fields) {
-
- $mdb2 = getConnection();
+
+ $mdb2 = getConnection();
global $user;
-
- $name = $fields['name'];
- if (!$name)
- return false;
- $client_id = (int) $fields['client_id'];
+ $name = $fields['name'];
+ if (!$name) return false;
- $invoice_date = new DateAndTime($user->date_format, $fields['date']);
+ $client_id = (int) $fields['client_id'];
+
+ $invoice_date = new DateAndTime($user->date_format, $fields['date']);
$date = $invoice_date->toString(DB_DATEFORMAT);
-
+
$start_date = new DateAndTime($user->date_format, $fields['start_date']);
$start = $start_date->toString(DB_DATEFORMAT);
-
+
$end_date = new DateAndTime($user->date_format, $fields['end_date']);
$end = $end_date->toString(DB_DATEFORMAT);
-
+
if (isset($fields['project_id'])) $project_id = (int) $fields['project_id'];
-
+
// Create a new invoice record.
$sql = "insert into tt_invoices (team_id, name, date, client_id)
- values($user->team_id, ".$mdb2->quote($name).", ".$mdb2->quote($date).", $client_id)";
+ values($user->team_id, ".$mdb2->quote($name).", ".$mdb2->quote($date).", $client_id)";
$affected = $mdb2->exec($sql);
- if (is_a($affected, 'PEAR_Error'))
- return false;
+ if (is_a($affected, 'PEAR_Error')) return false;
// Mark associated invoice items with invoice id.
$last_id = 0;
$res = $mdb2->query($sql);
$val = $res->fetchRow();
$last_id = $val['last_insert_id'];
-
+
// Our update sql is different depending on tracking mode.
if (MODE_TIME == $user->tracking_mode) {
// In "time only" tracking mode there is a single user rate.
} else {
// sql part for project id.
if ($project_id) $project_part = " and l.project_id = $project_id";
-
+
// When we have projects, rates are defined for each project in tt_user_project_binds.
$sql = "update tt_log l
left join tt_user_project_binds upb on (upb.user_id = l.user_id and upb.project_id = l.project_id)
$affected = $mdb2->exec($sql);
if (is_a($affected, 'PEAR_Error'))
return false;
-
+
// sql part for project id.
if ($project_id) $project_part = " and project_id = $project_id";
-
+
$sql = "update tt_expense_items set invoice_id = $last_id where client_id = $client_id $project_part and invoice_id is NULL
and date >= ".$mdb2->quote($start)." and date <= ".$mdb2->quote($end)." and cost <> 0 and status = 1";
$affected = $mdb2->exec($sql);
return (!is_a($affected, 'PEAR_Error'));
}
-
+
// prepareInvoiceBody - prepares an email body for invoice.
static function prepareInvoiceBody($invoice_id, $comment)
{
- global $user;
- global $i18n;
-
- $invoice = ttInvoiceHelper::getInvoice($invoice_id);
- $client = ttClientHelper::getClient($invoice['client_id'], true);
- $invoice_items = ttInvoiceHelper::getInvoiceItems($invoice_id);
-
- $tax_percent = $client['tax'];
-
- $subtotal = 0;
+ global $user;
+ global $i18n;
+
+ $invoice = ttInvoiceHelper::getInvoice($invoice_id);
+ $client = ttClientHelper::getClient($invoice['client_id'], true);
+ $invoice_items = ttInvoiceHelper::getInvoiceItems($invoice_id);
+
+ $tax_percent = $client['tax'];
+
+ $subtotal = 0;
$tax = 0;
foreach($invoice_items as $item)
$subtotal += $item['cost'];
if ($tax_percent) {
$tax_expenses = in_array('et', explode(',', $user->plugins));
foreach($invoice_items as $item) {
- if ($item['type'] == 2 && !$tax_expenses)
- continue;
- $tax += round($item['cost'] * $tax_percent / 100, 2);
+ if ($item['type'] == 2 && !$tax_expenses)
+ continue;
+ $tax += round($item['cost'] * $tax_percent / 100, 2);
}
}
- $total = $subtotal + $tax;
-
+ $total = $subtotal + $tax;
+
$subtotal = htmlspecialchars($user->currency).' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($subtotal, 2)));
if ($tax) $tax = htmlspecialchars($user->currency).' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($tax, 2)));
$total = htmlspecialchars($user->currency).' '.str_replace('.', $user->decimal_mark, sprintf('%8.2f', round($total, 2)));
-
+
if ('.' != $user->decimal_mark) {
foreach ($invoice_items as &$item) {
$item['cost'] = str_replace('.', $user->decimal_mark, $item['cost']);
unset($item); // Unset the reference. If we don't, the foreach loop below modifies the array while printing.
// See http://stackoverflow.com/questions/8220399/php-foreach-pass-by-reference-last-element-duplicating-bug
}
-
+
// Define some styles to use in email.
$style_title = 'text-align: center; font-size: 15pt; font-family: Arial, Helvetica, sans-serif;';
$style_tableHeader = 'font-weight: bold; background-color: #a6ccf7; text-align: left;';
$style_tableHeaderCentered = 'font-weight: bold; background-color: #a6ccf7; text-align: center;';
-
+
// Start creating email body.
$body = '<html>';
$body .= '<head><meta http-equiv="content-type" content="text/html; charset='.CHARSET.'"></head>';
$body .= '<body>';
-
+
// Output title.
$body .= '<p style="'.$style_title.'">'.$i18n->getKey('title.invoice').' '.htmlspecialchars($invoice['name']).'</p>';
-
+
// Output comment.
if($comment) $body .= '<p>'.htmlspecialchars($comment).'</p>';
-
+
// Output invoice info.
$body .= '<table>';
$body .= '<tr><td><b>'.$i18n->getKey('label.date').':</b> '.$invoice['date'].'</td></tr>';
$body .= '<tr><td><b>'.$i18n->getKey('label.client').':</b> '.htmlspecialchars($client['name']).'</td></tr>';
$body .= '<tr><td><b>'.$i18n->getKey('label.client_address').':</b> '.htmlspecialchars($client['address']).'</td></tr>';
$body .= '</table>';
-
+
$body .= '<p></p>';
-
+
// Output invoice items.
$body .= '<table border="0" cellpadding="4" cellspacing="0" width="100%">';
$body .= '<tr>';
}
$body .= '<tr><td colspan="'.$colspan.'" align="right"><b>'.$i18n->getKey('label.total').':</b></td><td nowrap align="right">'.$total.'</td></tr>';
$body .= '</table>';
-
+
// Output footer.
$body .= '<p style="text-align: center;">'.$i18n->getKey('form.mail.footer').'</p>';
// Finish creating email body.