<commit_before><?php namespace Blindern\UKA\Billett;

use \Carbon\Carbon;
use \Henrist\LaravelApiQuery\ApiQueryInterface;

class Order extends \Eloquent implements ApiQueryInterface {
    /**
     * How long a incomplete reservation is valid
     */
    const EXPIRE_INCOMPLETE_RESERVATION = 600;

    /**
     * How long a reservation that is sent to payment is valid
     */
    const EXPIRE_LOCKED_RESERVATION = 1800;

    /**
     * Create reservation
     *
     * @param Eventgroup $eg
     * @param array(array(Ticketgroup, int ticket-count), ...)
     * @param bool by ticket office
     * @return Order
     */
    public static function createReservation(Eventgroup $eg, $is_admin = false)
    {
        $order = new static();

        $order->eventgroup()->associate($eg);
        $order->time = time();
        $order->ip = $_SERVER['REMOTE_ADDR'];
        $order->browser = $_SERVER['HTTP_USER_AGENT'];
        $order->is_admin = $is_admin;
        $order->save();
        $order->generateOrderTextId();

        return $order;
    }

    /**
     * Find order by text id or fail
     */
    public static function findByTextIdOrFail($text_id)
    {
        if (empty($text_id))
        {
            throw new \ModelNotFoundException('Missing text id');
        }

        return static::where('order_text_id', $text_id)->firstOrFail();
    }

    /**
     * Get query for expired reservations
     */
    public static function expiredReservations()
    {
        return static::where('is_valid', false)->where('is_admin', false)->where(function ($q) {
            $q->where('is_locked', true)->where('time', '<', time() - static::EXPIRE_LOCKED_RESERVATION);
        })->orWhere(function ($q) {
            $q->where('is_locked', false)->where('time', '<', time() - static::EXPIRE_INCOMPLETE_RESERVATION);
        });
    }

    protected $model_suffix = '';
    protected $table = 'orders';
    //protected $appends = array('total_amount');

    protected $apiAllowedFields = array('id', 'eventgroup_id', 'order_text_id', 'is_valid', 'is_admin', 'time', 'ip', 'browser', 'name', 'email', 'phone', 'recruiter', 'total_amount', 'comment');
    protected $apiAllowedRelations = array('eventgroup', 'tickets', 'payments');

    public function eventgroup()
    {
        return $this->belongsTo('\\Blindern\\UKA\\Billett\\Eventgroup'.$this->model_suffix, 'eventgroup_id');
    }

    public function tickets()
    {
        return $this->hasMany('\\Blindern\\UKA\\Billett\\Ticket'.$this->model_suffix, 'order_id');
    }

    public function payments()
    {
        return $this->hasMany('\\Blindern\\UKA\\Billett\\Payment'.$this->model_suffix, 'order_id');
    }

    /**
     * Send tickets and receipt to email
     */
    public function sendEmail()
    {
        $events = $this->getEvents();

        \Mail::send(array('text' => 'billett.email_order'), array('order' => $this), function($message) use ($events)
        {
            $event = count($events) == 1 ? $events[0] : null;
            $eventinfo = $event ? " - ".$event->title." (".Carbon::createFromTimeStamp($event->time_start)->format('d.m.Y').")" : '';

            $message->to($this->email, $this->name);
            $message->subject('Billett'.(count($this->tickets) == 1 ? '' : 'er').' UKA på Blindern #'.$this->order_text_id.$eventinfo);

            $message->attachData($this->generateTicketsPdf(), 'billetter_'.$this->order_text_id.'.pdf', array('mime' => 'application/pdf'));
        });
    }

    /**
     * Generate PDF with all valid tickets
     */
    public function generateTicketsPdf()
    {
        return Ticket::generateTicketsPdf(array_filter($this->tickets->all(), function ($ticket) {
            return $ticket->is_valid && !$ticket->is_revoked;
        }));
    }

    /**
     * Get list of events this order is referenced with
     */
    public function getEvents()
    {
        $this->load('tickets.event');
        return array_reduce($this->tickets->all(), function ($prev, $ticket) {
            if (!in_array($ticket->event, $prev)) $prev[] = $ticket->event;
            return $prev;
        }, []);
    }

    /**
     * Check if order is a reservation
     */
    public function isReservation()
    {
        return !$this->isCompleted();
    }

    /**
     * Check if reservation has expired
     */
    public function hasExpired()
    {
        if (!$this->isReservation() || $this->is_admin) return false;
        return time() > $this->time + $this->getExpireDelay();
    }

    /**
     * Get count of seconds for the expiration
     */
    public function getExpireDelay()
    {
        return ($this->isReservationLocked()
                ? static::EXPIRE_LOCKED_RESERVATION
                : static::EXPIRE_INCOMPLETE_RESERVATION);
    }

    /**
     * Check if order has completed (valid order)
     */
    public function isCompleted()
    {
        return $this->is_valid == true;
    }

    /**
     * Validate ownership to reservation
     *
     * When the reservation is created, the ID is stored to the session
     */
    public function isOwnerOfReservation()
    {
        $orders = \Session::get('billett_reservations', array());
        foreach ($orders as $order_id) {
            if ($order_id == $this->id)
                return true;
        }
        return false;
    }

    /**
     * Check if reservation is locked for changes
     */
    public function isReservationLocked()
    {
        return $this->is_locked == true;
    }

    /**
     * Calculate total price for order
     */
    public function getTotalAmount()
    {
        $tickets = $this->tickets()->with('ticketgroup')->get();

        $amount = 0;
        foreach ($tickets as $ticket) {
            if ($ticket->is_revoked) continue;
            $amount += $ticket->ticketgroup->price + $ticket->ticketgroup->fee;
        }

        return $amount;
    }

    public function getTotalAmountAttribute() {
        return $this->getTotalAmount();
    }

    /**
     * Delete reservation
     *
     * Will also delete associated tickets
     */
    public function deleteReservation()
    {
        if ($this->isCompleted()) {
            throw new Exception("Trying to delete a order that is completed!");
        }

        \DB::transaction(function() {
            foreach ($this->tickets()->get() as $ticket) {
                $ticket->delete();
            }

            $this->delete();
        });

        return true;
    }

    /**
     * Generate order text-ID
     */
    protected function generateOrderTextId()
    {
        if ($this->order_text_id != null) {
            throw new Exception("Trying to create new order text-id, but it exists already!");
        }

        $d = \Carbon\Carbon::createFromTimeStamp($this->time);
        $orderid = $d->format("y").str_pad($d->format("z"), 3, "0", STR_PAD_LEFT).str_pad($this->id, 4, "0", STR_PAD_LEFT).(\Config::get('dibs.test') ? '-TEST' : '');

        $this->order_text_id = $orderid;
        $this->save();
    }

    /**
     * Try to mark reservation as in pay mode
     */
    public function placeOrder()
    {
        if ($this->isCompleted()) return false;
        if (empty($this->name) || empty($this->email) || empty($this->phone)) return false;

        if (!$this->isReservationLocked()) {
            $this->is_locked = true;
            $this->save();
        }

        $this->renew();

        return true;
    }

    /**
     * Update time for reservation
     *
     * Will check if still valid
     */
    public function renew()
    {
        if (!$this->canRenew()) {
            return false;
        }

        foreach ($this->tickets as $ticket) {
            if ($ticket->expire) {
                $ticket->expire = time() + $this->getExpireDelay();
                $ticket->save();
            }
        }

        $this->time = time();
        $this->save();

        return true;
    }

    /**
     * Check if we can renew the order
     *
     * @throws \Exception
     * @return boolean
     */
    public function canRenew()
    {
        if (!$this->isReservation()) throw new \Exception("The order is not a reservation");

        if (!$this->hasExpired()) return true;

        // check if the tickets are still available
        $groups = [];
        foreach ($this->tickets()->where('is_valid', 0)->with('ticketgroup', 'event')->get() as $ticket) {
            if (!isset($groups[$ticket->ticketgroup->id])) {
                $groups[$ticket->ticketgroup->id] = array($ticket->ticketgroup, 0);
            }
            $groups[$ticket->ticketgroup->id][1]++;
        }

        if (!Ticketgroup::checkIsAvailable($groups)) {
            return false;
        }

        return true;
    }

    /**
     * Mark order as complete, in practise changing from reservation to actual order
     */
    public function markComplete()
    {
        if ($this->isCompleted()) throw new \Exception("The order is already marked complete");

        if (!$this->canRenew()) return false;

        foreach ($this->tickets as $ticket) {
            if (!$ticket->is_valid) {
                $ticket->setValid();
            }
        }

        $this->time = time();
        $this->is_valid = true;
        $this->save();

        return true;
    }

    /**
     * Create and attach tickets (tickets will be saved)
     *
     * @param array list of [Ticketgroup, count] representing each new ticketgroup and number of tickets, eager load of event is preferred
     * @return array list of Ticket, the new tickets created
     */
    public function createTickets(array $ticketgroups)
    {
        $tickets = [];
        foreach ($ticketgroups as $row) {
            for ($i = 0; $i < $row[1]; $i++) {
                $ticket = new Ticket;
                $ticket->event()->associate($row[0]->event);
                $ticket->ticketgroup()->associate($row[0]);
                $ticket->order()->associate($this);

                if (!$this->is_admin) {
                    $ticket->expire = time() + static::EXPIRE_INCOMPLETE_RESERVATION;
                }

                $ticket->save();
                $tickets[] = $ticket;
            }
        }

        return $tickets;
    }

    /**
     * Hard refresh of balance
     */
    public function refreshBalance() {
        DB::statement('
            UPDATE
                orders, (
                    SELECT order_id, SUM(amount) amount
                    FROM
                        (
                            SELECT t.order_id, -g.price - g.fee AS amount
                            FROM tickets t JOIN ticketgroups g ON t.ticketgroup_id = g.id
                            WHERE t.order_id = ? AND t.is_valid = 1 AND t.is_revoked = 0
                            UNION ALL
                            SELECT order_id, amount AS amount
                            FROM payments
                            WHERE order_id = ?
                        ) ref
                    GROUP BY order_id
                ) amounts
            SET orders.balance = amounts.amount
            WHERE orders.id = amounts.order_id', [$this->id, $this->id]);

        $this->balance = DB::table('orders')->where('id', $this->id)->pluck('balance');
    }

    /**
     * Modify balance
     */
    public function modifyBalance($amount)
    {
        $this->balance += $amount;

        // make sure we have no races
        DB::statement('
            UPDATE orders
            SET balance = balance + ?
            WHERE id = ?', [$amount, $this->id]);
    }

    /**
     * Get fields we can search in
     */
    public function getApiAllowedFields() {
        return $this->apiAllowedFields;
    }

    /**
     * Get fields we can use as relations
     */
    public function getApiAllowedRelations() {<commit_msg>Fix missing namespace for DB-usage<commit_after><?php namespace Blindern\UKA\Billett;

use \Carbon\Carbon;
use \Henrist\LaravelApiQuery\ApiQueryInterface;

class Order extends \Eloquent implements ApiQueryInterface {
    /**
     * How long a incomplete reservation is valid
     */
    const EXPIRE_INCOMPLETE_RESERVATION = 600;

    /**
     * How long a reservation that is sent to payment is valid
     */
    const EXPIRE_LOCKED_RESERVATION = 1800;

    /**
     * Create reservation
     *
     * @param Eventgroup $eg
     * @param array(array(Ticketgroup, int ticket-count), ...)
     * @param bool by ticket office
     * @return Order
     */
    public static function createReservation(Eventgroup $eg, $is_admin = false)
    {
        $order = new static();

        $order->eventgroup()->associate($eg);
        $order->time = time();
        $order->ip = $_SERVER['REMOTE_ADDR'];
        $order->browser = $_SERVER['HTTP_USER_AGENT'];
        $order->is_admin = $is_admin;
        $order->save();
        $order->generateOrderTextId();

        return $order;
    }

    /**
     * Find order by text id or fail
     */
    public static function findByTextIdOrFail($text_id)
    {
        if (empty($text_id))
        {
            throw new \ModelNotFoundException('Missing text id');
        }

        return static::where('order_text_id', $text_id)->firstOrFail();
    }

    /**
     * Get query for expired reservations
     */
    public static function expiredReservations()
    {
        return static::where('is_valid', false)->where('is_admin', false)->where(function ($q) {
            $q->where('is_locked', true)->where('time', '<', time() - static::EXPIRE_LOCKED_RESERVATION);
        })->orWhere(function ($q) {
            $q->where('is_locked', false)->where('time', '<', time() - static::EXPIRE_INCOMPLETE_RESERVATION);
        });
    }

    protected $model_suffix = '';
    protected $table = 'orders';
    //protected $appends = array('total_amount');

    protected $apiAllowedFields = array('id', 'eventgroup_id', 'order_text_id', 'is_valid', 'is_admin', 'time', 'ip', 'browser', 'name', 'email', 'phone', 'recruiter', 'total_amount', 'comment');
    protected $apiAllowedRelations = array('eventgroup', 'tickets', 'payments');

    public function eventgroup()
    {
        return $this->belongsTo('\\Blindern\\UKA\\Billett\\Eventgroup'.$this->model_suffix, 'eventgroup_id');
    }

    public function tickets()
    {
        return $this->hasMany('\\Blindern\\UKA\\Billett\\Ticket'.$this->model_suffix, 'order_id');
    }

    public function payments()
    {
        return $this->hasMany('\\Blindern\\UKA\\Billett\\Payment'.$this->model_suffix, 'order_id');
    }

    /**
     * Send tickets and receipt to email
     */
    public function sendEmail()
    {
        $events = $this->getEvents();

        \Mail::send(array('text' => 'billett.email_order'), array('order' => $this), function($message) use ($events)
        {
            $event = count($events) == 1 ? $events[0] : null;
            $eventinfo = $event ? " - ".$event->title." (".Carbon::createFromTimeStamp($event->time_start)->format('d.m.Y').")" : '';

            $message->to($this->email, $this->name);
            $message->subject('Billett'.(count($this->tickets) == 1 ? '' : 'er').' UKA på Blindern #'.$this->order_text_id.$eventinfo);

            $message->attachData($this->generateTicketsPdf(), 'billetter_'.$this->order_text_id.'.pdf', array('mime' => 'application/pdf'));
        });
    }

    /**
     * Generate PDF with all valid tickets
     */
    public function generateTicketsPdf()
    {
        return Ticket::generateTicketsPdf(array_filter($this->tickets->all(), function ($ticket) {
            return $ticket->is_valid && !$ticket->is_revoked;
        }));
    }

    /**
     * Get list of events this order is referenced with
     */
    public function getEvents()
    {
        $this->load('tickets.event');
        return array_reduce($this->tickets->all(), function ($prev, $ticket) {
            if (!in_array($ticket->event, $prev)) $prev[] = $ticket->event;
            return $prev;
        }, []);
    }

    /**
     * Check if order is a reservation
     */
    public function isReservation()
    {
        return !$this->isCompleted();
    }

    /**
     * Check if reservation has expired
     */
    public function hasExpired()
    {
        if (!$this->isReservation() || $this->is_admin) return false;
        return time() > $this->time + $this->getExpireDelay();
    }

    /**
     * Get count of seconds for the expiration
     */
    public function getExpireDelay()
    {
        return ($this->isReservationLocked()
                ? static::EXPIRE_LOCKED_RESERVATION
                : static::EXPIRE_INCOMPLETE_RESERVATION);
    }

    /**
     * Check if order has completed (valid order)
     */
    public function isCompleted()
    {
        return $this->is_valid == true;
    }

    /**
     * Validate ownership to reservation
     *
     * When the reservation is created, the ID is stored to the session
     */
    public function isOwnerOfReservation()
    {
        $orders = \Session::get('billett_reservations', array());
        foreach ($orders as $order_id) {
            if ($order_id == $this->id)
                return true;
        }
        return false;
    }

    /**
     * Check if reservation is locked for changes
     */
    public function isReservationLocked()
    {
        return $this->is_locked == true;
    }

    /**
     * Calculate total price for order
     */
    public function getTotalAmount()
    {
        $tickets = $this->tickets()->with('ticketgroup')->get();

        $amount = 0;
        foreach ($tickets as $ticket) {
            if ($ticket->is_revoked) continue;
            $amount += $ticket->ticketgroup->price + $ticket->ticketgroup->fee;
        }

        return $amount;
    }

    public function getTotalAmountAttribute() {
        return $this->getTotalAmount();
    }

    /**
     * Delete reservation
     *
     * Will also delete associated tickets
     */
    public function deleteReservation()
    {
        if ($this->isCompleted()) {
            throw new Exception("Trying to delete a order that is completed!");
        }

        \DB::transaction(function() {
            foreach ($this->tickets()->get() as $ticket) {
                $ticket->delete();
            }

            $this->delete();
        });

        return true;
    }

    /**
     * Generate order text-ID
     */
    protected function generateOrderTextId()
    {
        if ($this->order_text_id != null) {
            throw new Exception("Trying to create new order text-id, but it exists already!");
        }

        $d = \Carbon\Carbon::createFromTimeStamp($this->time);
        $orderid = $d->format("y").str_pad($d->format("z"), 3, "0", STR_PAD_LEFT).str_pad($this->id, 4, "0", STR_PAD_LEFT).(\Config::get('dibs.test') ? '-TEST' : '');

        $this->order_text_id = $orderid;
        $this->save();
    }

    /**
     * Try to mark reservation as in pay mode
     */
    public function placeOrder()
    {
        if ($this->isCompleted()) return false;
        if (empty($this->name) || empty($this->email) || empty($this->phone)) return false;

        if (!$this->isReservationLocked()) {
            $this->is_locked = true;
            $this->save();
        }

        $this->renew();

        return true;
    }

    /**
     * Update time for reservation
     *
     * Will check if still valid
     */
    public function renew()
    {
        if (!$this->canRenew()) {
            return false;
        }

        foreach ($this->tickets as $ticket) {
            if ($ticket->expire) {
                $ticket->expire = time() + $this->getExpireDelay();
                $ticket->save();
            }
        }

        $this->time = time();
        $this->save();

        return true;
    }

    /**
     * Check if we can renew the order
     *
     * @throws \Exception
     * @return boolean
     */
    public function canRenew()
    {
        if (!$this->isReservation()) throw new \Exception("The order is not a reservation");

        if (!$this->hasExpired()) return true;

        // check if the tickets are still available
        $groups = [];
        foreach ($this->tickets()->where('is_valid', 0)->with('ticketgroup', 'event')->get() as $ticket) {
            if (!isset($groups[$ticket->ticketgroup->id])) {
                $groups[$ticket->ticketgroup->id] = array($ticket->ticketgroup, 0);
            }
            $groups[$ticket->ticketgroup->id][1]++;
        }

        if (!Ticketgroup::checkIsAvailable($groups)) {
            return false;
        }

        return true;
    }

    /**
     * Mark order as complete, in practise changing from reservation to actual order
     */
    public function markComplete()
    {
        if ($this->isCompleted()) throw new \Exception("The order is already marked complete");

        if (!$this->canRenew()) return false;

        foreach ($this->tickets as $ticket) {
            if (!$ticket->is_valid) {
                $ticket->setValid();
            }
        }

        $this->time = time();
        $this->is_valid = true;
        $this->save();

        return true;
    }

    /**
     * Create and attach tickets (tickets will be saved)
     *
     * @param array list of [Ticketgroup, count] representing each new ticketgroup and number of tickets, eager load of event is preferred
     * @return array list of Ticket, the new tickets created
     */
    public function createTickets(array $ticketgroups)
    {
        $tickets = [];
        foreach ($ticketgroups as $row) {
            for ($i = 0; $i < $row[1]; $i++) {
                $ticket = new Ticket;
                $ticket->event()->associate($row[0]->event);
                $ticket->ticketgroup()->associate($row[0]);
                $ticket->order()->associate($this);

                if (!$this->is_admin) {
                    $ticket->expire = time() + static::EXPIRE_INCOMPLETE_RESERVATION;
                }

                $ticket->save();
                $tickets[] = $ticket;
            }
        }

        return $tickets;
    }

    /**
     * Hard refresh of balance
     */
    public function refreshBalance() {
        \DB::statement('
            UPDATE
                orders, (
                    SELECT order_id, SUM(amount) amount
                    FROM
                        (
                            SELECT t.order_id, -g.price - g.fee AS amount
                            FROM tickets t JOIN ticketgroups g ON t.ticketgroup_id = g.id
                            WHERE t.order_id = ? AND t.is_valid = 1 AND t.is_revoked = 0
                            UNION ALL
                            SELECT order_id, amount AS amount
                            FROM payments
                            WHERE order_id = ?
                        ) ref
                    GROUP BY order_id
                ) amounts
            SET orders.balance = amounts.amount
            WHERE orders.id = amounts.order_id', [$this->id, $this->id]);

        $this->balance = \DB::table('orders')->where('id', $this->id)->pluck('balance');
    }

    /**
     * Modify balance
     */
    public function modifyBalance($amount)
    {
        $this->balance += $amount;

        // make sure we have no races
        \DB::statement('
            UPDATE orders
            SET balance = balance + ?
            WHERE id = ?', [$amount, $this->id]);
    }

    /**
     * Get fields we can search in
     */
    public function getApiAllowedFields() {
        return $this->apiAllowedFields;
    }

    /**
     * Get fields we can use as relations
     */
    public function getApiAllowedRelations() {
{'fraction_non_alphanumeric': 0.14540059347181009, 'fraction_numerical': 0.0020347604917337857, 'mean_word_length': 1.9157088122605364, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 271, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1649371', 'n_tokens_mistral': 6958, 'n_tokens_neox': 6601, 'n_words': 1562}