<?php

  /**
   * Invoices class
   *
   * @package activeCollab.modules.invoicing
   * @subpackage models
   */
  class Invoices extends BaseInvoices {
    
    /**
     * Returns true if $user can create new invoices
     * 
     * @param IUser $user
     * @return boolean
     */
    static function canAdd(IUser $user) {
      return $user->isFinancialManager();
    } // canAdd
    
    /**
     * Returns true if $user can manage invoices
     * 
     * @param IUser $user
     * @return boolean
     */
    static function canManage(IUser $user) {
      return $user->isFinancialManager();
    } // canManage

    /**
     * Returns true if $user can access $company invoices
     *
     * @param User $user
     * @param Company $company
     * @return boolean
     */
    static function canAccessCompanyInvoices($user, Company $company) {
      return $user->isFinancialManager() || ($company->isManager($user) && !$company->isOwner());
    } // canAccessCompanyInvoices

    /**
     * Return true is system should notify client about new payment
     * 
     * @return Boolean
     */
    static function getNotifyClientAboutNewPayment() {
      return boolval(ConfigOptions::getValue('invoice_notify_on_payment'));
    }//getNotifyClientAboutNewPayment
    
    /**
     * Return true is system should notify client when invoice canceled
     * 
     * @return Boolean
     */
    static function getNotifyClientAboutCanceledInvoice() {
      return boolval(ConfigOptions::getValue('invoice_notify_on_cancel'));
    }//getNotifyClientAboutCanceledInvoice
    
    
    // ---------------------------------------------------
    //  Utils
    // ---------------------------------------------------

    /**
     * Cached print invoice as value
     *
     * @var string
     */
    static private $print_invoice_as = null;

    /**
     * Return label used for invoices
     *
     * @return string
     */
    static function printInvoiceAs() {
      if(self::$print_invoice_as === null) {
        self::$print_invoice_as = trim(ConfigOptions::getValue('print_invoices_as'));

        if(empty(self::$print_invoice_as)) {
          self::$print_invoice_as = lang('Invoice');
        } // if
      } // if

      return self::$print_invoice_as;
    } // printInvoiceAs

    /**
     * Cached print invoice as value
     *
     * @var string
     */
    static private $print_proforma_invoice_as = null;

    /**
     * Return label used for proforma invoices
     *
     * @return string
     */
    static function printProformaInvoiceAs() {
      if(self::$print_proforma_invoice_as === null) {
        self::$print_proforma_invoice_as = trim(ConfigOptions::getValue('print_proforma_invoices_as'));

        if(empty(self::$print_proforma_invoice_as)) {
          self::$print_proforma_invoice_as = lang('Proforma Invoice');
        } // if
      } // if

      return self::$print_proforma_invoice_as;
    } // printProformaInvoiceAs
    
    // ---------------------------------------------------
    //  Finders
    // ---------------------------------------------------
    
    /**
     * Find invoices by ID-s
     * 
     * @param array $ids
     * @return DBResult
     */
    static function findByIds($ids) {
      return Invoices::find(array(
        'conditions' => array('id IN (?)', $ids),
      ));
    } // findByIds
    
    /**
     * Return number of draft invoices
     *
     * @return integer
     */
    static function countDrafts() {
      return Invoices::count(array('status = ?', INVOICE_STATUS_DRAFT));
    } // countDrafts
    
    /**
     * Count overdue invoices (if company is provided, then it counts for that specified company)
     *
     * @param Copmany $company
     * @return integer
     */
    static function countOverdue($company = null) {
      $today = new DateValue(time() + get_user_gmt_offset());
      if ($company instanceof Company) {
        return Invoices::count(array('status = ? AND due_on < ? AND company_id = ?', INVOICE_STATUS_ISSUED, $today, $company->getId()));
      } else {
        return Invoices::count(array('status = ? AND due_on < ?', INVOICE_STATUS_ISSUED, $today));
      } // if
    } // countOverdue
    
    /**
     * Find overdue invoices (if company is provided, only invoices for that companies are returned)
     *
     * @param Copmany $company
     * @return integer
     */
    static function findOverdue($company = null) {
      $today = new DateValue(time() + get_user_gmt_offset());
      if ($company instanceof Company) {
        return Invoices::find(array(
          'conditions' => array('status = ? AND due_on < ? AND company_id = ?', INVOICE_STATUS_ISSUED, $today, $company->getId()),
          'order' => 'due_on DESC',
        ));
      } else {
        return Invoices::find(array(
          'conditions' => array('status = ? AND due_on < ?', INVOICE_STATUS_ISSUED, $today),
          'order' => 'due_on DESC',
        ));
      } // if
    } // findOverdue
    
    /**
     * Count outstanding (overdue invoices are excluded. If company is provided outstanding invoices for that company are counted)
     *
     * @param Company $company
     * @return integer
     */
    static function countOutstanding($company = null) {
      $today = new DateValue(time() + get_user_gmt_offset());
      if ($company instanceof Company) {
        return Invoices::count(array('status = ? AND due_on >= ? AND company_id = ?', INVOICE_STATUS_ISSUED, $today, $company->getId()));
      } else {
        return Invoices::count(array('status = ? AND due_on >= ?', INVOICE_STATUS_ISSUED, $today));
      } // if
    } // countOutstanding
    
    /**
     * Return outstanding invoices (overdue invoices are excluded. If company is provided outstanding invoices for that company are counted)
     *
     * @param Company $company
     * @return array;
     */
    static function findOutstanding($company = null) {
      $today = new DateValue(time() + get_user_gmt_offset());
      if ($company instanceof Company) {
        return Invoices::find(array(
          'conditions' => array('status = ? AND due_on >= ? AND company_id = ?', INVOICE_STATUS_ISSUED, $today, $company->getId()),
          'order' => 'due_on DESC',
        ));
      } else {
        return Invoices::find(array(
          'conditions' => array('status = ? AND due_on >= ?', INVOICE_STATUS_ISSUED, $today),
          'order' => 'due_on DESC',
        ));
      } // if
    } // findOutstanding
        
    /**
     * Return invoices by company
     *
     * @param Company $company
     * @param User $user
     * @param array $statuses
     * @Param string $order_by
     * @return array
     */
    static function findByCompany(Company &$company, User $user, $statuses = null, $order_by = 'created_on') {
      if (is_null($statuses)) {
        $statuses = self::getVisibleStatuses($user);
      } // if

      return Invoices::find(array(
        'conditions' => array('company_id = ? AND status IN (?)', $company->getId(), $statuses),
        'order' => $order_by,
      ));
    } // findByCompany
    
    /**
     * Count invoices by company
     *
     * @param Company $company
     * @param User $user
     * @param array $statuses
     * @return integer
     */
    function countByCompany(&$company, User $user, $statuses = null) {
      if (is_null($statuses)) {
        $statuses = self::getVisibleStatuses($user);
      } // if

      return Invoices::count(array('company_id = ? AND status IN (?)', $company->getId(), $statuses));
    } // countByCompany
    
    /**
     * Return ID-s by company
     * 
     * @param Company $company
     * @param User $user
     * @return array
     */
    static function findIdsByCompany(Company $company, User $user) {
      return DB::executeFirstRow('SELECT id FROM ' . TABLE_PREFIX . 'invoices WHERE company_id = ? AND status IN (?)', $company->getId(), self::getVisibleStatuses($user));
    } // findIdsByCompany
    
    /**
     * Return summarized company invoices information
     *
     * @param User $user
     * @return array
     */
    function findInvoicedCompaniesInfo(User $user, $statuses = null) {
      $companies_table = TABLE_PREFIX . 'companies';
      $invoices_table = TABLE_PREFIX . 'invoices';

      if (is_null($statuses)) {
        $statuses = self::getVisibleStatuses($user);
      } // if
      
      return DB::execute("SELECT $companies_table.id, $companies_table.name, COUNT($invoices_table.id) AS 'invoices_count' FROM $companies_table, $invoices_table WHERE $invoices_table.company_id = $companies_table.id AND $invoices_table.status IN (?) GROUP BY $invoices_table.company_id ORDER BY $companies_table.name ", $statuses);
    } // findInvoicedCompaniesInfo
    
    /**
     * Return number of invoices that use $currency
     *
     * @param Currency $currency
     * @return integer
     */
    static function countByCurrency($currency) {
      return Invoices::count(array('currency_id = ?', $currency->getId()));
    } // countByCurrency

    
    /**
     * Increment invoice counters
     *
     * @param integer $year
     * @param integer $month
     * @return boolean
     */
    static function incrementDateInvoiceCounters($year = null, $month = null) {
      if ($year === null) {
        $year = date('Y');
      } // if
      
      if ($month === null) {
        $month = date('n');
      } // if
      
      $counters = ConfigOptions::getValue('invoicing_number_date_counters');
      
      $previous_month_counter = array_var($counters, $year.'_'.$month, 0);
      $previous_year_counter =  array_var($counters, $year, 0);
      $previous_total_counter = array_var($counters, 'total', 0);
      
      $counters[$year.'_'.$month] = ($previous_month_counter + 1);
      $counters[$year] = ($previous_year_counter + 1);
      $counters['total'] = ($previous_total_counter + 1);
      return ConfigOptions::setValue('invoicing_number_date_counters', $counters);
    } // incrementDateInvoiceCounters

    /**
     * Retrieves invoice counters
     *
     * @param integer $year
     * @param integer $month
     * @return array
     */
    static function getDateInvoiceCounters($year = null, $month = null) {
      if ($year === null) {
        $year = date('Y');
      } // if
      
      if ($month === null) {
        $month = date('n');
      } // if
      
      $counters = ConfigOptions::getValue('invoicing_number_date_counters');
      
      $previous_month_counter = array_var($counters, $year.'_'.$month, 0);
      $previous_year_counter =  array_var($counters, $year, 0);
      $previous_total_counter = array_var($counters, 'total', 0);
      
      return array($previous_total_counter, $previous_year_counter, $previous_month_counter);
    } // getDateInvoiceCounters
    
    /**
     * Sets invoice counters
     *
     * @param array of int $counters
     * @return array
     */
    static function setDateInvoiceCounters($total_counter = null, $year_counter = null, $month_counter = null) {
      $year = date('Y');
      $month = date('n');
      
      $counters = self::getDateInvoiceCounters();
      
      if($total_counter) {
        $counters['total'] = $total_counter;
      } // if
      
      if($month_counter) {
        $counters["{$year}_{$month}"] = $month_counter;
      } // if
      
      if($year_counter) {
        $counters[$year] = $year_counter;
      } // if
      
      return ConfigOptions::setValue('invoicing_number_date_counters', $counters);
    } // setDateInvoiceCounters
    
    /**
     * Get array of statuses
     * 
     * @return array
     */
    static function getStatusMap() {
      return array(
        INVOICE_STATUS_DRAFT => lang('Draft'),
        INVOICE_STATUS_ISSUED => lang('Issued'),
        INVOICE_STATUS_PAID => lang('Paid'),
        INVOICE_STATUS_CANCELED => lang('Canceled')
      );
    } // getStatusMap

    /**
     * Get array of statuses that $user can see
     *
     * @param User $user
     * @return array
     */
    function getVisibleStatuses(User $user) {
      $statuses = array(INVOICE_STATUS_CANCELED, INVOICE_STATUS_ISSUED, INVOICE_STATUS_PAID);
      if ($user->isFinancialManager()) {
        $statuses[] = INVOICE_STATUS_DRAFT;
      } // if

      return $statuses;
    } // getVisibleStatuses

    /**
     * Make the map of unique month-year => verbose pairs for given invoices
     *
     * @param array $invoices
     * @return array
     */
    function getIssuedAndDueDatesMap(&$invoices) {
      $map = array(
        'issued_on' => array(),
        'due_on' => array(),
      );

      if (is_foreachable($invoices)) {
        foreach ($invoices as &$invoice) {
          if ($invoice['issued_on_month'] && !array_key_exists($invoice['issued_on_month'], $map['issued_on'])) {
            $map['issued_on'][$invoice['issued_on_month']] = date("F Y", strtotime($invoice['issued_on_month'] . '-1'));
          } // if

          if ($invoice['due_on_month'] && !array_key_exists($invoice['due_on_month'], $map['due_on'])) {
            $map['due_on'][$invoice['due_on_month']] = date("F Y", strtotime($invoice['due_on_month'] . '-1'));
          } // if
        } // foreach
      } // if

      // sort maps
      krsort($map['issued_on'], $sort_function);
      krsort($map['due_on'], $sort_function);

      return $map;
    } // getIssuedAndDueDatesMap
    
    
    /**
     * Return array of issued on month
     * 
     * @return array
     * 
     */
    static function mapIssuedOnMonth(Company $company = null) {
      if($company instanceof Company) {
        $invoices = self::find(array(
          'conditions' => array('company_id = ? AND issued_on IS NOT ?', $company->getId(), null),
        	'order' => 'issued_on desc'
        ));
      } else {
         $invoices = self::find(array(
          'conditions' => array('issued_on IS NOT ?', null),
        	'order' => 'issued_on desc'
        ));
      }//if
      $map = array();
      if(is_foreachable($invoices)) {
        foreach($invoices as $invoice) {
          if($invoice->getStatus() > 0) {
            $map[$invoice->getIssuedOnMonth()] = date('F, Y', strtotime($invoice->getIssuedOn())+get_user_gmt_offset());
          }//if
        }//foreach
      }//if
      return $map;
    }//mapIssuedOnMonth
    

    /**
     * Return array of due on month
     * 
     * @return array
     * 
     */
    static function mapDueOnMonth(Company $company = null) {
      if($company instanceof Company) {
        $invoices = self::find(array(
        	'conditions' => array('company_id = ?', $company->getId()),
        	'order' => 'due_on desc'
        ));
      } else {
        $invoices = self::find(array(
        	'order' => 'due_on desc'
        ));
      }
      $map = array();
      if(is_foreachable($invoices)) {
        foreach($invoices as $invoice) {
          if($invoice->getStatus() > 0) {
            $map[$invoice->getDueOnMonth()] = date('F, Y', strtotime($invoice->getDueOn())+get_user_gmt_offset());
          }//if
        }//foreach
      }//if
      return $map;
    }//mapIssuedOnMonth
    
    
    
    /**
     * Finds the invoices for the objects list - general rule is to resemble the ApplicationObject::describe result
     *
     * @param Company $company
     * @return array
     */
    static function findForObjectsList(User $user, $company = null) {
      $result = array();
      
      if ($company instanceof Company) {
        $invoices_table = TABLE_PREFIX . 'invoices';
        $invoices = DB::execute("SELECT $invoices_table.id, $invoices_table.company_id, $invoices_table.issued_on, $invoices_table.due_on, $invoices_table.number as name, status, $invoices_table.created_on FROM $invoices_table WHERE $invoices_table.company_id = ? AND status IN (?) ORDER BY $invoices_table.created_on DESC", $company->getId(), self::getVisibleStatuses($user));

        $view_invoice_url_template = Router::assemble('people_company_invoice', array('company_id' => '--COMPANYID--', 'invoice_id' => '--INVOICEID--'));
      } else {
        $invoices_table = TABLE_PREFIX . 'invoices';
        $companies_table = TABLE_PREFIX.'companies';
        $invoices = DB::execute("SELECT $invoices_table.id, $invoices_table.company_id, $invoices_table.issued_on, $invoices_table.due_on, $companies_table.name AS company_name, $invoices_table.number as name, status, $invoices_table.created_on FROM $invoices_table,$companies_table WHERE $invoices_table.company_id = $companies_table.id AND $invoices_table.status IN (?) ORDER BY $invoices_table.created_on DESC", self::getVisibleStatuses($user));

        $view_invoice_url_template = Router::assemble('invoice', array('invoice_id' => '--TEMPLATE--'));
      } // if

      if (is_foreachable($invoices)) {
        foreach ($invoices as $invoice) {
          $result[] = array(
            'id'						=> $invoice['id'],
            'name'					=> $invoice['name'] ? $invoice['name'] : lang('Draft #:invoice_num', array('invoice_num' => $invoice['id'])),
            'long_name'			=> $invoice['name'] ? lang('Invoice :name', array('name' => $invoice['name'])) : lang('Invoice Draft #:invoice_num', array('invoice_num' => $invoice['id'])),
            'client_id'			=> $invoice['company_id'],
            'client_name'		=> $invoice['company_name'],
            'issued_on_month' => $invoice['issued_on'] ? date('Y-m', strtotime($invoice['issued_on'])+get_user_gmt_offset()) : '',
            'due_on_month'    => $invoice['due_on'] ? date('Y-m', strtotime($invoice['due_on'])+get_user_gmt_offset()) : '',
            'status'				=> $invoice['status'],
            'permalink'			=> $company instanceof Company
                                ? str_replace(array('--COMPANYID--', '--INVOICEID--'), array($company->getId(), $invoice['id']), $view_invoice_url_template)
                                : str_replace('--TEMPLATE--', $invoice['id'], $view_invoice_url_template)
          );
        } // foreach
      } // if

    	
      return $result;
    } // findForObjectsList
    
    /**
     * Finds the invoices for the phone list view
     *
     * @param User $user
     * @param Company $company
     * @return array
     */
    static function findForPhoneList(User $user, $company = null) {
    	$invoices_table = TABLE_PREFIX . 'invoices';
      $companies_table = TABLE_PREFIX.'companies';
    	
    	if($company instanceof Company) {
        $invoices = DB::execute("SELECT $invoices_table.id, $invoices_table.company_id, $invoices_table.number as name, status FROM $invoices_table WHERE $invoices_table.company_id = ? AND $invoices_table.status IN (?) ORDER BY $invoices_table.status, $invoices_table.id", $company->getId(), self::getVisibleStatuses($user));
        $view_invoice_url_template = Router::assemble('people_company_invoice', array('company_id' => '--COMPANYID--', 'invoice_id' => '--INVOICEID--'));
      } else {
        $invoices = DB::execute("SELECT $invoices_table.id, $invoices_table.company_id, $invoices_table.number as name, status FROM $invoices_table, $companies_table WHERE $invoices_table.company_id = $companies_table.id AND $invoices_table.status IN (?) ORDER BY $invoices_table.status, $invoices_table.id", self::getVisibleStatuses($user));
        $view_invoice_url_template = Router::assemble('invoice', array('invoice_id' => '--TEMPLATE--'));
      } // if
      
			$result = array();
			
      if(is_foreachable($invoices)) {
      	foreach($invoices as $invoice) {
					$result[$invoice['status']][] = array(
						'name' => $invoice['name'] ? $invoice['name'] : lang('Draft #:invoice_num', array('invoice_num' => $invoice['id'])),
						'permalink' => $company instanceof Company ? str_replace(array('--COMPANYID--', '--INVOICEID--'), array($company->getId(), $invoice['id']), $view_invoice_url_template) : str_replace('--TEMPLATE--', $invoice['id'], $view_invoice_url_template)
					);
      	} // foreach
      } // if
    	
      return $result;
    } // findForPhoneList
    
    /**
     * Find invoices for printing by grouping and filtering criteria
     *
     * @param User $user
     * @param Company $company
     * @param string $group_by
     * @param array $filter_by
     * @return DBResult
     */
    static public function findForPrint(User $user, $company = null, $group_by = null, $filter_by = null) {
      
      if (!in_array($group_by, array('client_id', 'status'))) {
      	$group_by = null;
      } // if
      if($group_by == 'client_id') {
        $group_by = 'company_id';
      }
                
      // filter by completion status
      $filter_is_completed = array_var($filter_by, 'status', null);
      if ($filter_is_completed === '0' && $user->isFinancialManager()) {
        $conditions[] = DB::prepare('(status=?)', array(INVOICE_STATUS_DRAFT));
      } else if ($filter_is_completed === '1') {
      	$conditions[] = DB::prepare('(status=?)', array(INVOICE_STATUS_ISSUED));
      } else if ($filter_is_completed === '2') {
      	$conditions[] = DB::prepare('(status=?)', array(INVOICE_STATUS_PAID));
      } else if ($filter_is_completed === '3') {
      	$conditions[] = DB::prepare('(status=?)', array(INVOICE_STATUS_CANCELED));
      } else {
        $conditions[] = DB::prepare('(status IN (?))', array(self::getVisibleStatuses($user)));
      } // if

      if ($company instanceof Company) {
        $conditions[] = DB::prepare('company_id = ?', array($company->getId()));
      } // if
      
      // do find invoices
      $invoices = self::find(array(
      	'conditions' => implode(' AND ', $conditions),
      	'order' => $group_by ? $group_by : 'id DESC' 
      ));
    	
    	return $invoices;
    } // findForPrint
    
  }