<?php

  /**
   * Quote class
   *
   * @package activeCollab.modules.invoicing
   * @subpackage models
   */
  class Quote extends BaseQuote implements IRoutingContext, IObjectContext, IComments, ISubscriptions, INotifierContext, IProjectBasedOn, IInvoiceBasedOn, IHistory {

    /**
     * Invoice implementation for this object
     * 
     * @var IInvoiceBasedOnQuoteImplementation
     */
    private $invoice;
    
    /**
     * Return invoice implementation
     * 
     * @return IInvoiceBasedOnImplementation
     */
    function invoice() {
      if(empty($this->invoice)) {
        $this->invoice = new IInvoiceBasedOnQuoteImplementation($this);
      }//if
      return $this->invoice;
    }//invoice
    
    
    /**
     * Cached company value
     *
     * @var Company
     */
    private $company = false;

    /**
      * Return invoice company
      *
      * @return Company
      */
    function getCompany() {
      if($this->company === false) {
        $company = Companies::findById($this->getCompanyId());
        if ($company instanceof Company && $company->getState() === 0) {
          $this->company = null;
        } else {
          $this->company = $company;
        } // if
      } // if

      return $this->company;
    } // getCompany
    
    /**
     * Get client company name
     * 
     * @return string
     */
    function getCompanyName() {
      $company = $this->getCompany();

      if ($company instanceof Company) {
        return $company->getName();
      } else {
        return $this->getFieldValue('company_name');
      } // if
    } // getCompanyName
    
    /**
     * Get client company address
     * 
     * @return string
     */
    function getCompanyAddress() {
      $company = $this->getCompany();

      if ($company instanceof Company) {
        $quote_company_address = parent::getCompanyAddress();
        $company_address = $company->getConfigValue('office_address');

        return !empty($quote_company_address) && $quote_company_address !== $company_address ? $quote_company_address : $company_address;
      } else {
        return parent::getCompanyAddress();
      } // if
    } // getCompanyAddress

    /**
     * Set client info based on data provided
     *
     * @param string $client_type
     * @param array $client_data
     * @param array $new_client_data
     */
    function setClientInfo($client_type, $client_data, $new_client_data) {
      switch ($client_type) {
        case 'new_client':
          $recipient = new AnonymousUser($new_client_data['recipient_name'], $new_client_data['recipient_email']);
          $this->setCompanyId(0);
          $this->setCompanyName($new_client_data['company_name']);
          $this->setCompanyAddress($new_client_data['company_address']);
          break;
        case 'existing_client':
          $recipient = Users::findById($client_data['recipient_id']);
          $company = Companies::findById($client_data['company_id']);
          $this->setCompanyId($client_data['company_id']);
          $this->setCompanyName($company->getName());
          $this->setCompanyAddress($client_data['company_address']);
          break;
        default:
          break;
      } // switch

      if ($recipient instanceof IUser) {
        $this->setRecipient($recipient);
      } else {
        throw new InvalidInstanceError("recipient", $recipient, "IUSer");
      } // if
    } // setClientInfo
    
    /**
     * Cached parent request object, if based on project request
     *
     * @var ProjectRequest
     */
    private $based_on = false;
    
    /**
     * Return parent project request, if set
     * 
     * @return ProjectRequest
     */
    function getBasedOn() {
      if($this->based_on === false) {
        $this->based_on = $this->getBasedOnId() ? ProjectRequests::findById($this->getBasedOnId()) : null;
      } // if
      
      return $this->based_on;
    } // getBasedOn

    /**
     * Cached project that has been created based on this quote
     *
     * @var bool|Project
     */
    private $project = false;

    /**
     * Get a project based on this quote (assume last created one)
     */
    function getProject() {
      if ($this->project === false) {
        $this->project = Projects::find(array(
          "conditions" => array("based_on_type = ? AND based_on_id = ?", get_class($this), $this->getId()),
          "order" => "created_on DESC",
          "one" => true
        ));
      }

      return $this->project;
    } // getProject

    /**
     * Cached date value
     *
     * @var DateTimeValue
     */
    protected $date = false;

    /**
     * Return date based on a given $status
     *
     * @param string $status
     * @return DateTimeValue
     */
    function getDate($status = 'sent') {
      switch($status) {
        case 'draft':
          $action_on = 'getCreatedOn';
          break;
        case 'sent':
          $action_on = 'getSentOn';
          break;
        case 'won':
          $action_on = 'getClosedOn';
          break;
        case 'lost':
          $action_on = 'getClosedOn';
          break;
        default:
          $action_on = 'getSentOn';
          break;
      } // switch

      if($this->date === false) {
        $this->date = $this->$action_on();
      } // if
      return $this->date;
    } // getDate

    /**
     * Cached quote total
     *
     * @var float
     */
    var $total = false;

    /**
     * Cached tax value
     *
     * @var float
     */
    var $tax = false;

    /**
     * Cached taxed total
     *
     * @var float
     */
    var $taxed_total = false;
    
    /**
     * Return quote total
     *
     * @param boolean $cache
     * @return float
     */
    function getTotal($cache = true) {
      $this->calculateTotal($cache);
      return $this->total;
    } // getTotal
    
    /**
     * Return calculated tax
     *
     * @param boolean $cache
     * @return float
     */
    function getTax($cache = true) {
      $this->calculateTotal($cache);
      return $this->tax;
    } // getTax
    
    /**
     * Return tax column text
     * 
     * @return string
     */
    function getTaxColumnText() {
      if($this->getTax() == 0) {
        $column_text = '';
      } else {
        $common_tax_rate = $this->commonTaxRate();
        
        if($common_tax_rate instanceof TaxRate) {
          $column_text = $common_tax_rate->getName() . " (" . $common_tax_rate->getVerbosePercentage() . ")";
        } else {
          $column_text = lang('Tax');
        }//if
      }//if
      
      return $column_text;
    } // getTaxColumnText
    
    /**
     * Return Tax Rate if one tax rate is used for all items, else return false
     * 
     * @return mixed
     */
    function commonTaxRate() {
      if($this->getTax() == 0) {
        //no tax
        return false;
      } else {
        //tax exists
        $items = $this->getItems();
        if(is_foreachable($items)) {
          $tax_rate = null;
          
          foreach($items as $item) {
            if($item->getTaxRate() instanceof TaxRate) {
              $rate[] = $item->getTaxRate()->getId();
              $tax_rate = $item->getTaxRate();
            } else {
              $rate[] = 0;
            }
          }//foreach
          
          $tax_arr = array_unique($rate);
          if(count($tax_arr) == 1) {
            return $tax_rate;
          }
          return false;
        }//if
      }//if
      
      return false;
    }//commonTaxRate

    /**
     * Returned taxed total
     *
     * @param boolean $cache
     * @return float
     */
    function getTaxedTotal($cache = true) {
      $this->calculateTotal($cache);
      return $this->taxed_total;
    } // getTaxedTotal
    
    /**
     * Calculate total by walking through list of items
     */
    function calculateTotal($cache = true) {
      if($cache === false || $this->total === false || $this->tax === false || $this->taxed_total === false) {
        $this->total = 0;
        $this->tax = 0;

        if(is_foreachable($this->getItems())) {
          foreach($this->getItems() as $item) {
            $this->total += $item->getSubTotal();
            $this->tax   += $item->getTax();
          } // foreach
        } // if
        
        // now we have total tax for quote, we need to round it up to 2 decimals
        $this->tax = round($this->tax, 2);
        $this->taxed_total = $this->total + $this->tax;
      } // if
    } // calculateTotal
    
    /**
     * Cached quote items
     *
     * @var array
     */
    private $items = false;

    /**
     * Return quote items
     *
     * @return array
     */
    function getItems() {
      if($this->items === false) {
        $this->items = QuoteItems::findByQuote($this);
      } // if
      return $this->items;
    } // getItems
    
    /**
     * Return currency code
     *
     * @return string
     */
    function getCurrencyCode() {
      return $this->getCurrency() instanceof Currency ? $this->getCurrency()->getCode() : lang('Unknown Currency');
    } // getCurrencyCode
    
    /**
     * Cached language value
     *
     * @var Language
     */
    var $language = false;

    /**
      * Return quote language
      *
      * @return Language
      */
    function getLanguage() {
      if($this->language === false) {
        $this->language = Languages::findById($this->getLanguageId());
      } // if
      return $this->language;
    } // getLanguage
    
    /**
     * Cached currency instance
     *
     * @var Currency
     */
    var $currency = false;

    /**
     * Return quote currency
     *
     * @return Currency
     */
    function getCurrency() {
      if($this->currency === false) {
        $this->currency = Currencies::findById($this->getCurrencyId());
      } // if
      return $this->currency;
    } // getCurrency

    /**
     * Set person who created $this quote
     *
     * $created_by can be an instance of User / AnonymousUser class or null
     *
     * @param mixed $created_by
     * @return mixed
     */
    function setCreatedBy($created_by) {
      if($created_by === null) {
        $this->setCreatedById(0);
        $this->setCreatedByName('');
        $this->setCreatedByEmail('');
      } elseif($created_by instanceof IUser) {
        $this->setCreatedById($created_by->getId());
        $this->setCreatedByName($created_by->getDisplayName());
        $this->setCreatedByEmail($created_by->getEmail());
      } else {
        throw new InvalidInstanceError('created_by', $created_by, 'IUser');
      } // if
      
      return $created_by;
    } // setCreatedBy
    
    /**
     * Cached sent by instance
     *
     * @var User
     */
    private $sent_by = false;
    
    /**
     * Return user who sent $this quote
     *
     * @return User
     */
    function getSentBy() {
      if($this->sent_by === false) {
        if($this->getSentById()) {
          $this->sent_by = Users::findById($this->getSentById());
        } // if
        
        if(!($this->sent_by instanceof User)) {
          $this->sent_by = new AnonymousUser($this->getSentByName(), $this->getSentByEmail());
        } // if
      } // if

      return $this->sent_by;
    } // getSentBy

    /**
     * Get verbose status for the quote
     *
     * @return string
     */
    function getVerboseStatus() {
      $status_map = Quotes::getStatusMap();
      return array_key_exists($this->getStatus(), $status_map) ? $status_map[$this->getStatus()] : $this->getStatus();
    } // getVerboseStatus

    /**
     * Cached value user that the quote is (or will be) sent to
     *
     * @var User
     */
    private $recipient = false;

    /**
     * Prepare recipient's user object based on the information provided
     *
     * @return IUser
     */
    function getRecipient() {
      if ($this->recipient === false) {
        $recipient_id = $this->getRecipientId();
        if ($recipient_id) {
          $this->recipient = Users::findById($recipient_id);
        } // if

        if (!$this->recipient instanceof User || $this->recipient->getState() === 0) {
          // hack because email field has been added later and nowhere to be populated from
          $recipient_email = is_valid_email($this->getRecipientEmail()) ? $this->getRecipientEmail() : 'unknown@domain.com';

          $this->recipient = new AnonymousUser($this->getRecipientName(), $recipient_email);
        } // if 
      } // if

      return $this->recipient;
    } // getRecipient

    /**
     * Save recipient's information
     *
     * @param mixed $recipient
     */
    function setRecipient($recipient) {
      if(!$recipient instanceof IUser) {
        $recipient = Users::findById($recipient);
      }//if

      if($recipient instanceof IUser) {
        $this->setRecipientEmail($recipient->getEmail());
        $this->setRecipientId($recipient->getId());
        $this->setRecipientName($recipient->getName());
      } else {
        throw new InvalidInstanceError('recipient', $recipient, 'IUser');
      } // if
    } // setRecipient
    
    /**
     * Cached sent to by instance
     *
     * @var User
     */
    private $sent_to = false;
    
    /**
     * Return user to which quote was sent to
     *
     * @return User
     */
    function getSentTo() {
      if($this->sent_to === false) {
        $this->sent_to = Users::findById($this->getSentToId());
      } // if
      return $this->sent_to;
    } // getSentTo
    
    /**
     * Cached closed by instance
     *
     * @var User
     */
    private $closed_by = false;
    
    /**
     * Return user who closed $this quote
     *
     * @return User
     */
    function getClosedBy() {
      if($this->closed_by === false) {
        $closed_by_id = $this->getClosedById();
        if($closed_by_id) {
          $this->closed_by = Users::findById($closed_by_id);
        } // if
        
        if(!($this->closed_by instanceof User)) {
          $this->closed_by = new AnonymousUser($this->getClosedByName(), $this->getClosedByEmail());
        } // if
      } // if
      return $this->closed_by;
    } // getClosedBy

    /**
     * Mark this quote as won
     *
     * @param User $by
     * @param boolean $save
     */
    function markAsWon(User $by, $save = true) {
      $this->setStatus(QUOTE_STATUS_WON);
      $this->setClosedOn(new DateTimeValue());
      $this->setClosedById($by->getId());
      $this->setClosedByName($by->getName());
      $this->setClosedByEmail($by->getEmail());

      if($save) {
        $this->save();
      } // if
    } // markAsWon

    /**
     * Mark this quote as lost
     *
     * @param User $by
     * @param boolean $save
     */
    function markAsLost(User $by, $save = true) {
      $this->setStatus(QUOTE_STATUS_LOST);
      $this->setClosedOn(new DateTimeValue());
      $this->setClosedById($by->getId());
      $this->setClosedByName($by->getName());
      $this->setClosedByEmail($by->getEmail());

      if($save) {
        $this->save();
      } // if
    } // markAsLost
    
    /**
     * Check if quote is draft
     * 
     * @return boolean
     */
    function isDraft() {
      return $this->getStatus() == QUOTE_STATUS_DRAFT;
    } // isDraft
    
    /**
     * Returns true if $this quote is being sent
     *
     * @return boolean
     */
    function isSent() {
      return $this->getStatus() == QUOTE_STATUS_SENT;
    } // isSent
    
    /**
     * Returns true if $this quote is won
     *
     * @return boolean
     */
    function isWon() {
       return $this->getStatus() == QUOTE_STATUS_WON;
    } // isWon
    
    /**
     * Returns true if $this quote is being lost
     *
     * @return boolean
     */
    function isLost() {
      return $this->getStatus() == QUOTE_STATUS_LOST;
    } // isLost

    /**
     * Check if public page for the quote is expired
     *
     * @return boolean
     */
    function isPublicPageExpired() {
      if($this->getStatus() > QUOTE_STATUS_SENT) {
        $status_updated_on = $this->getDate($this->getStatus());

        if($status_updated_on instanceof DateTimeValue) {
          return (time() - $status_updated_on->getTimestamp()) >= (7 * 86400);
        } // if
      } // if

      return false;
    } // isPublicPageExpired
    
    /**
     * Copy quote comments as estimate discussion
     *
     * @param Project $to
     * @param User $by
     */
    function copyComments(Project $to, User $by) {
      $comments = Comments::findByObject($this);

      if($comments) {
        try {
          DB::beginWork('Copy quote comments to a project @ ' . __CLASS__);
  
          $new_discussion = new Discussion();
  
          $new_discussion->setProjectId($to->getId());
          $new_discussion->setName('Estimate Discussion');
          $new_discussion->setBody($this->getName());
          $new_discussion->setState(STATE_VISIBLE);
          $new_discussion->setVisibility(VISIBILITY_NORMAL);
          $new_discussion->setCreatedBy($by);
          $new_discussion->save();
  
          foreach($comments as $comment) {
            if($comment instanceof QuoteComment) {
              $new_comment = new ProjectObjectComment();

              $new_comment->setParentType('Discussion');
              $new_comment->setParentId($new_discussion->getId());
              $new_comment->setBody($comment->getBody());
              $new_comment->setState(STATE_VISIBLE);
              $new_comment->setVisibility(VISIBILITY_NORMAL);
              $new_comment->setCreatedBy($comment->getCreatedBy());
              $new_comment->save();
            } // if
          } // foreach
  
          DB::commit('Quote comments copied to a project @ ' . __CLASS__);
        } catch(Exception $e) {
          DB::rollback('Failed to copy quote comments to a project @ ' . __CLASS__);
          throw $e;
        } // try
      } // if
    } // copyComments
    
    /**
     * Create Milestones based on Quote Items
     *
     * @param Project $project
     * @param iUser $by
     */
    function createMilestones(Project $project, User $by) {
      $items = $this->getItems();
      if(!is_foreachable($items)) {
        return true;
      } // if

      try {
        DB::beginWork('Create Milestones based on Quote Items in project @ ' . __CLASS__);

        foreach($items as $item) {
          if($item instanceof QuoteItem) {
            $new_milestone = new Milestone();

            $new_milestone->setProjectId($project->getId());
            $new_milestone->setName($item->getDescription());
            $new_milestone->setState(STATE_VISIBLE);
            $new_milestone->setVisibility(VISIBILITY_NORMAL);
            $new_milestone->setCreatedBy($by);
            $new_milestone->setDueOn($item->getDueOn());
            $new_milestone->setDateField1($item->getStartOn());
            $new_milestone->save();
          } // if
        } // foreach

        DB::commit('Milestones based on Quote Items created in project @ ' . __CLASS__);
      } catch(Exception $e) {
        DB::rollback('Failed to create Milestones based on Quote Items in project @ ' . __CLASS__);
        throw $e;
      } // try
    } // createMilestones
    
    /**
     * Close $this quote that's used $by user to create a project
     *
     * @param User $by
     * @param integer $status
     */
    function close(User $by, $status = null) {
      $status = is_null($status) ? QUOTE_STATUS_WON : $status;
      
      $this->setStatus($status);
      $this->setClosedOn(new DateTimeValue());
      $this->setClosedById($by->getId());
      $this->setClosedByName($by->getDisplayName());
      $this->setClosedByEmail($by->getEmail());
      
      $this->save();
    } // close
    
    // ---------------------------------------------------
    //  Interface implementations
    // ---------------------------------------------------
    
    /**
     * Return routing context name
     *
     * @return string
     */
    function getRoutingContext() {
      return 'quote';
    } // getRoutingContext
    
    /**
     * Return routing context parameters
     *
     * @return mixed
     */
    function getRoutingContextParams() {
      return array('quote_id' => $this->getId());
    } // getRoutingContextParams
    
    /**
     * Return object context domain
     * 
     * @return string
     */
    function getObjectContextDomain() {
      return 'quotes';
    } // getObjectContextDomain
    
    /**
     * Return object context path
     * 
     * @return string
     */
    function getObjectContextPath() {
      return 'quotes/' . $this->getId();
    } // getObjectContextPath
    
    /**
     * Cached comment interface instance
     *
     * @var QuoteComment
     */
    private $comments;
    
    /**
     * Return quote comments interface instance
     *
     * @return IQuoteCommentsImplementation
     */
    function comments() {
      if(empty($this->comments)) {
        $this->comments = new IQuoteCommentsImplementation($this);
      } // if
      return $this->comments;
    } // comments
    
    /**
     * Cached attachment manager instance
     *
     * @var QuoteAttachment
     */
    private $attachments;
    
    /**
     * Return attachments manager instance for this object
     *
     * @return IQuoteAttachmentsImplementation
     */
    function attachments() {
      if(empty($this->attachments)) {
        $this->attachments = new IQuoteAttachmentsImplementation($this);
      } // if
      
      return $this->attachments;
    } // attachments
    
    /**
     * Subscriptions helper instance
     *
     * @var IQuoteSubscriptionsImplementation
     */
    private $subscriptions;
    
    /**
     * Return subscriptions helper for this object
     *
     * @return IQuoteSubscriptionsImplementation
     */
    function subscriptions() {
      if(empty($this->subscriptions)) {
        $this->subscriptions = new IQuoteSubscriptionsImplementation($this);
      } // if
      
      return $this->subscriptions;
    } // subscriptions
    
    /**
     * Return email notification context ID
     *
     * @return string
     */
    function getNotifierContextId() {
      return 'QUOTE/' . $this->getId();
    } // getNotifierContextId
    
    /**
     * Cached history implementation instance
     *
     * @var IHistoryImplementation
     */
    private $history = false;
    
    /**
     * Return history helper instance
     * 
     * @return IHistoryImplementation
     */
    function history() {
      if($this->history === false) {
        $this->history = new IHistoryImplementation($this, array('company_id', 'company_name', 'company_address', 'currency_id', 'language_id', 'note', 'private_note', 'status'));
      } // if
      
      return $this->history;
    } // history
    
    // ------------------------------------------------------------
    //  Workaround that meets comments implementation prerequisites
    // ------------------------------------------------------------
    
    /**
     * Return project object visibility
     *
     * @return integer
     */
    function getVisibility() {
      return VISIBILITY_NORMAL;
    } // getVisibility
    
    /**
     * Return project request state
     *
     * @return integer
     */
    function getState() {
      return STATE_VISIBLE;
    } // getState
    
    // ---------------------------------------------------
    //  Permissions
    // ---------------------------------------------------
    
    /**
     * Returns true if $user can access $company quotes
     *
     * @param iUser $user
     * @param Company $company
     * @return boolean
     */
    function canAccessCompanyQuotes(iUser $user, Company $company) {
      if (Quotes::canManage($user)) {
        return true;
      } else {
        return $company instanceof Company ? $company->isManager($user) : false;
      } // if
    } // canAccessCompanyQuotes
    
    /**
     * Return true if $user create an invoice based on $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canCreateInvoice(IUser $user) {
      return ($this->isWon() || $this->isSent()) && Invoices::canAdd($user);
    } // canCreateInvoice
    
    /**
     * Return true if $user create project based on $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canCreateProject(iUser $user) {
      return Quotes::canManage($user) && Projects::canAdd($user) && ($this->isSent() || $this->isWon());
    } // canCreateProject
    
    /**
     * Return true if $user can access $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canView(iUser $user) {
      return Quotes::canManage($user) || ($this->canAccessCompanyQuotes($user, $this->getCompany()) && $this->getStatus() > QUOTE_STATUS_DRAFT);
    } // canView
    
    /**
     * Return true if $user can edit $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canEdit(iUser $user) {
      return ($this->isDraft() || $this->isSent()) && Quotes::canManage($user);
    } // canEdit
    
    /**
     * Return true if $user can send $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canSend(iUser $user) {
      return ($this->isDraft() || $this->isSent() || $this->isWon()) && Quotes::canManage($user);
    } // canSend
    
    /**
     * Return true if $user can won $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canWon(iUser $user) {
      //return $this->isSent() && Quotes::canManage($user);
      return !$this->isWon() && Quotes::canManage($user);
    } // canWon
    
    /**
     * Return true if $user can lost $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canLost(iUser $user) {
      //return $this->isSent() && Quotes::canManage($user);
      return !$this->isLost() && Quotes::canManage($user);
    } // canLost
    
    /**
     * Return true if $user can delete $this quote
     *
     * @param iUser $user
     * @return boolean
     */
    function canDelete(iUser $user) {
      return Quotes::canManage($user);
    } // canDelete
    
    // ---------------------------------------------------
    //  Options
    // ---------------------------------------------------

    /**
     * Prepare list of options that $user can use
     *
     * @param IUser $user
     * @param NamedList $options
     * @param string $interface
     * @return NamedList
     */
    protected function prepareOptionsFor(IUser $user, NamedList $options, $interface = AngieApplication::INTERFACE_DEFAULT) {      
      if($this->canView($user) && !$this->isLost()) {
        $options->add('view_pdf', array(
          'text' => $this->getStatus() == QUOTE_STATUS_DRAFT ? lang('Preview PDF') : lang('View PDF'),
          'url' => Quotes::canManage($user) ? $this->getPdfUrl() : $this->getCompanyPdfUrl(),
          'onclick' => new TargetBlankCallback(),
        ), true);
      } // if

      if($this->canSend($user)) {
        $options->add('send_quote', array(
          'text' => $this->isDraft() ? lang('Send') : lang('Resend Quote'),
          'url' => $this->getSendUrl(),
          'important' => true,
          'onclick' => new FlyoutFormCallback('quote_sent', array(
            'width' => 'narrow',
            'success_event' => $this->getUpdatedEventName(),
            'success_message' => lang('Quote has been sent')
           ))
        ), true);
      } // if

      if($this->canWon($user)) {
        $options->add('quote_won', array(
         'text' => lang('Mark as Won'),
          'url' => $this->getWonUrl(),
          'important' => true,
          'onclick' => new AsyncLinkCallback(array(
            'confirmation' => lang('Are you sure that you want to mark this request as won?'),
            'success_message' => lang('Quote has been marked as won'),
            'success_event' => $this->getUpdatedEventName()
          ))
        ), true);
      } // if

      if($this->canLost($user)) {
        $options->add('quote_lost', array(
         'text' => lang('Mark as Lost'),
          'url' => $this->getLostUrl(),
          'important' => true,
          'onclick' => new AsyncLinkCallback(array(
            'confirmation' => lang('Are you sure that you want to mark this request as lost?'),
            'success_message' => lang('Quote has been marked as lost'),
            'success_event' => $this->getUpdatedEventName()
          ))
        ), true);
      } // if

      if (Quotes::canAdd($user)) {
        $options->add('duplicate_quote', array(
          'url' => Router::assemble('quotes_add', array('duplicate_quote_id' => $this->getId())),
          'text' => lang('Duplicate'),
          'onclick' => new FlyoutFormCallback('quote_created'),
          'icon' => AngieApplication::getImageUrl('icons/12x12/duplicate-invoice.png', INVOICING_MODULE),
        ), true);
      } // if
      
      if($this->canCreateInvoice($user)) {
        $options->add('make_invoice', array(
          'url' => $this->invoice()->getUrl(),
          'text' => lang('Create Invoice'),
          'onclick' => new FlyoutFormCallback('create_invoice_from_quote'),
          'important' => true
        ));
      } // if
      
      if($this->canCreateProject($user)) {
        $options->add('create_project', array(
          'text' => lang('Convert to Project'),
          'url' => $this->getCreateProjectUrl(),
          'onclick' => new FlyoutFormCallback('create_project_from_quote'),
          'important' => $this->isWon(),
        ), true);
      } // if

      if($this->canDelete($user)) {
        $options->add('delete', array(
          'text' => lang('Delete'),
          'url' => $this->getDeleteUrl(),
          'onclick' => new AsyncLinkCallback(array(
            'confirmation' => lang('Are you sure that you want to delete this quote permanently?'), 
            'success_message' => lang('Quote has been successfully deleted'), 
            'success_event' => $this->getDeletedEventName(),
          ))
        ), true);
      } // if
      
      parent::prepareOptionsFor($user, $options, $interface);
    } // prepareOptionsFor
  
    // ---------------------------------------------------
    //  URL-s
    // ---------------------------------------------------

    /**
     * Return the link to the form for saving new client info
     *
     * @return string
     */
    function getSaveClientUrl() {
      return Router::assemble('quote_save_client', array(
        'quote_id' => $this->getId()
      ));
    } // getSaveClientUrl

    /**
     * Return quote's public URL
     *
     * @return string
     */
    function getPublicUrl() {
      return Router::assemble('quote_check', array(
        'quote_public_id' => $this->getPublicId(),
      ));
    } // getPublicUrl
    
    /**
     * Return company view URL
     *
     * @return string
     */
    function getCompanyViewUrl() {
      return Router::assemble('company_quote', array(
        'quote_id' => $this->getId(),
        'company_id' => $this->getCompanyId()
      ));
    } // getCompanyViewUrl
    
    /**
     * Return PDF document URL
     *
     * @return string
     */
    function getPdfUrl() {
      return Router::assemble('quote_pdf', array_merge((array) $this->getRoutingContextParams(), array(
     		'force' => true,
     		'disposition' => 'attachment'
      )));
    } // getPdfUrl
    
    /**
     * URL to quote's publicly available PDF
     *
     * @return string
     */
    function getPublicPdfUrl() {
      return Router::assemble('quote_public_pdf', array(
      	'quote_public_id' => $this->getPublicId(),
     		'force' => true,
     		'disposition' => 'attachment'
      ));
    } // getPublicPdfUrl
    
    /**
     * Return PDF document URL accessible from company quotes page
     *
     * @return string
     */
    function getCompanyPdfUrl() {
      if ($this->getCompanyId()) {
        return Router::assemble('company_quote_pdf', array(
          'quote_id' => $this->getId(),
          'company_id' => $this->getCompanyId(),
	     		'force' => true,
	     		'disposition' => 'attachment'
        ));
      } else {
        return false;
      } // if
    } // getCompanyPdfUrl
    
    /**
     * Return send quote URL
     *
     * @return string
     */
    function getSendUrl() {
      return Router::assemble('quote_send', $this->getRoutingContextParams());
    } // getSendUrl
    
    /**
     * Return quote won URL
     *
     * @return string
     */
    function getWonUrl() {
      return Router::assemble('quote_won', $this->getRoutingContextParams());
    } // getWonUrl
    
    /**
     * Return company quote won URL
     *
     * @return string
     */
    function getCompanyWonUrl() {
      return Router::assemble('company_quote_won', array(
        'quote_id' => $this->getId(),
        'company_id' => $this->getCompanyId()
      ));
    } // getCompanyViewUrl
    
    /**
     * Return create an invoice based on $this quote URL
     *
     * @return string
     */
    function getCreateInvoiceUrl() {
      return Router::assemble('invoices_add', array(
        'quote_id' => $this->getId()
      ));
    } // getCreateInvoiceUrl
    
    /**
     * Return create a project based on $this quote URL
     *
     * @return string
     */
    function getCreateProjectUrl() {
      return Router::assemble('projects_add', $this->getRoutingContextParams());
    } // getCreateProjectUrl
    
    /**
     * Return quote lost URL
     *
     * @return string
     */
    function getLostUrl() {
      return Router::assemble('quote_lost', $this->getRoutingContextParams());
    } // getLostUrl
    
    /**
     * Return notify client URL
     *
     * @return string
     */
    function getNotifyUrl() {
      return Router::assemble('quote_notify', $this->getRoutingContextParams());
    } // getNotifyUrl
    
    // ---------------------------------------------------
    //  System
    // ---------------------------------------------------
    
    /**
     * Return array or property => value pairs that describes this object
     *
     * $user is an instance of user who requested description - it's used to get
     * only the data this user can see
     *
     * @param IUser $user
     * @param boolean $detailed
     * @param boolean $for_interface
     * @return array
     */
    function describe(IUser $user, $detailed = false, $for_interface = false) {
      $result = parent::describe($user, $detailed, $for_interface);
      
      $currency = $this->getCurrency();
      
      $result['currency'] = array(
        'id' => $this->getCurrencyId(),
        'code' => $currency->getCode(),
        'name' => $currency->getName()
      );
      
      $result['status'] = (int) $this->getStatus();
      $result['verbose_status'] = $this->getVerboseStatus();
      
      $result['sent_on'] = $this->getSentOn();
      $result['closed_on'] = $this->getClosedOn();

      $result['public_url'] = $this->getStatus() >= QUOTE_STATUS_SENT && $this->getPublicId() && (Quotes::canManage($user) || !$this->isPublicPageExpired()) ? $this->getPublicUrl() : false;

      $result['based_on'] = $this->getBasedOn() instanceof ProjectRequest ? $this->getBasedOn()->describe($user) : null;
      $result['based_on_type'] = $this->getBasedOnType();
      $result['based_on_id'] = $this->getBasedOnId();      

      $result['client'] = $this->getCompany() instanceof Company ? $this->getCompany()->describe($user) : array('id' => Inflector::slug($this->getCompanyName()), 'name' => $this->getCompanyName());
      $result['company_address'] = $this->getCompanyAddress();
      $result['client_id'] = $this->getCompanyId() > 0 ? $this->getCompanyId() : Inflector::slug($this->getCompanyName());

      $result['recipient'] = $this->getRecipient();
      
      $result['note'] = $this->getNote();
      $result['private_note'] = Quotes::canManage($user) ? $this->getPrivateNote() : '';
      
      $result['subtotal'] = $this->getTotal(false);
      $result['tax'] = $this->getTax(false);
      $result['total'] = $this->getTaxedTotal(false);
      
      // Items
      $result['items'] = array();
      
      if($this->getItems()) {
        foreach($this->getItems() as $item) {
          $result['items'][] = $item->describe($user, false, $for_interface);
        } // foreach
      } // if
      
      return $result;
    } // describe

    /**
     * Return array or property => value pairs that describes this object
     *
     * @param IUser $user
     * @param boolean $detailed
     * @return array
     */
    function describeForApi(IUser $user, $detailed = false) {
      $result = parent::describeForApi($user, $detailed);

      $currency = $this->getCurrency();

      $result['currency'] = array(
        'id' => $this->getCurrencyId(),
        'code' => $currency->getCode(),
        'name' => $currency->getName()
      );

      $result['status'] = (int) $this->getStatus();
      $result['verbose_status'] = $this->getVerboseStatus();

      $result['sent_on'] = $this->getSentOn();
      $result['closed_on'] = $this->getClosedOn();

      $result['public_url'] = $this->getStatus() >= QUOTE_STATUS_SENT && $this->getPublicId() && (Quotes::canManage($user) || !$this->isPublicPageExpired()) ? $this->getPublicUrl() : false;

      $result['based_on'] = $this->getBasedOn() instanceof ProjectRequest ? $this->getBasedOn()->describe($user) : null;
      $result['based_on_type'] = $this->getBasedOnType();
      $result['based_on_id'] = $this->getBasedOnId();

      $result['client'] = $this->getCompany() instanceof Company ? $this->getCompany()->describe($user) : array('id' => Inflector::slug($this->getCompanyName()), 'name' => $this->getCompanyName());
      $result['company_address'] = $this->getCompanyAddress();
      $result['client_id'] = $this->getCompanyId() > 0 ? $this->getCompanyId() : Inflector::slug($this->getCompanyName());

      $result['recipient'] = $this->getRecipient();

      $result['note'] = $this->getNote();
      $result['private_note'] = Quotes::canManage($user) ? $this->getPrivateNote() : '';

      $result['subtotal'] = $this->getTotal(false);
      $result['tax'] = $this->getTax(false);
      $result['total'] = $this->getTaxedTotal(false);

      // Items
      $result['items'] = array();

      if($this->getItems()) {
        foreach($this->getItems() as $item) {
          $result['items'][] = $item->describeForApi($user);
        } // foreach
      } // if

      return $result;
    } // describeForApi

    /**
     * Validate before saving
     *
     * @param ValidationErrors $errors
     */
    function validate(ValidationErrors &$errors) {
      if(!$this->validatePresenceOf('name')) {
        $errors->addError(lang('Quote summary is required'), 'name');
      } // if
    } // validate
    
    /**
     * Delete existing quote
     *
     * @return boolean
     */
    function delete() {
      try {
        DB::beginWork('Removing quote @ ' . __CLASS__);

        parent::delete();
        QuoteItems::deleteByQuote($this);
        
        DB::commit('Quote removed @ ' . __CLASS__);
      } catch(Exception $e) {
        DB::rollback('Failed to remove quote @ ' . __CLASS__);

        throw $e;
      } // try
    } // delete
    
  }