<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Contributions related routes
 *
 * PHP version 5
 *
 * Copyright © 2014-2016 The Galette Team
 *
 * This file is part of Galette (http://galette.tuxfamily.org).
 *
 * Galette is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Galette is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Routes
 * @package   Galette
 *
 * @author    Johan Cwiklinski <johan@x-tnd.be>
 * @copyright 2014-2016 The Galette Team
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
 * @version   SVN: $Id$
 * @link      http://galette.tuxfamily.org
 * @since     0.8.2dev 2014-11-27
 */

use Galette\Entity\Contribution;
use Galette\Repository\Contributions;
use Galette\Entity\Transaction;
use Galette\Repository\Transactions;
use Galette\Repository\Members;
use Galette\Entity\Adherent;
use Galette\Entity\ContributionsTypes;
use Galette\Core\GaletteMail;
use Galette\Entity\Texts;
use Galette\IO\PdfContribution;
use Galette\Repository\PaymentTypes;

$app->get(
    '/{type:transactions|contributions}[/{option:page|order|member}/{value:\d+|all}]',
    function ($request, $response, $args) {
        $ajax = false;
        if ($request->isXhr()
            || isset($request->getQueryParams()['ajax'])
            && $request->getQueryParams()['ajax'] == 'true'
        ) {
            $ajax = true;
        }
        $get = $request->getQueryParams();

        $option = null;
        if (isset($args['option'])) {
            $option = $args['option'];
        }
        $value = null;
        if (isset($args['value'])) {
            $value = $args['value'];
        }

        $raw_type = null;
        switch ($args['type']) {
            case 'transactions':
                $raw_type = 'transactions';
                break;
            case 'contributions':
                $raw_type = 'contributions';
                break;
        }

        $filter_name = 'filter_' . $raw_type;

        if (isset($this->session->$filter_name) && $ajax === false) {
            $filters = $this->session->$filter_name;
        } else {
            $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type . 'List');
            $filters = new $filter_class();
        }

        //member id
        $id_adh = null;
        if (isset($get[Adherent::PK]) && $get[Adherent::PK] > 0) {
            $id_adh = (int)$get[Adherent::PK];
            $filters->filtre_cotis_adh = $id_adh;
        }

        $max_amount = null;
        if (isset($request->getQueryParams()['max_amount'])) {
            $filters->filtre_transactions = true;
            $filters->max_amount = (int)$request->getQueryParams()['max_amount'];
        } else {
            $filters->filtre_transactions = false;
            $filters->max_amount = null;
        }

        if ($option !== null) {
            switch ($option) {
                case 'page':
                    $filters->current_page = (int)$value;
                    break;
                case 'order':
                    $filters->orderby = $value;
                    break;
                case 'member':
                    if (($this->login->isAdmin()
                        || $this->login->isStaff())
                    ) {
                        if ($value == 'all') {
                            $filters->filtre_cotis_adh = null;
                        } else {
                            $filters->filtre_cotis_adh = $value;
                        }
                    }
                    break;
            }
        }

        if (!$this->login->isAdmin() && !$this->login->isStaff()) {
            $filters->filtre_cotis_adh = $this->login->id;
        }

        $class = '\\Galette\\Repository\\' . ucwords($raw_type);
        $contrib = new $class($this->zdb, $this->login, $filters);
        $contribs_list = $contrib->getList(true);

        //store filters into session
        if ($ajax === false) {
            $this->session->$filter_name = $filters;
        }

        //assign pagination variables to the template and add pagination links
        $filters->setSmartyPagination($this->router, $this->view->getSmarty());

        $tpl_vars = [
            'page_title'        => $raw_type === 'contributions' ?
                                    _T("Contributions management") :
                                    _T("Transactions management"),
            'require_dialog'    => true,
            'require_calendar'  => true,
            'contribs'          => $contrib,
            'list'              => $contribs_list,
            'nb'                => $contrib->getCount(),
            'filters'           => $filters,
            'mode'              => ($ajax === true ? 'ajax' : 'std')
        ];

        if ($filters->filtre_cotis_adh != null) {
            $member = new Adherent($this->zdb);
            $member->load($filters->filtre_cotis_adh);
            $tpl_vars['member'] = $member;
        }

        // display page
        $this->view->render(
            $response,
            'gestion_' . $raw_type . '.tpl',
            $tpl_vars
        );
        return $response;
    }
)->setName('contributions')->add($authenticate);

$app->post(
    '/{type:contributions|transactions}/filter',
    function ($request, $response, $args) {
        $raw_type = null;
        switch ($args['type']) {
            case 'transactions':
                $raw_type = 'transactions';
                break;
            case 'contributions':
                $raw_type = 'contributions';
                break;
        }

        $type = 'filter_' . $raw_type;
        $post = $request->getParsedBody();
        $error_detected = [];

        if ($this->session->$type !== null) {
            $filters = $this->session->$type;
        } else {
            $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type) . 'List';
            $filters = new $filter_class();
        }

        if (isset($post['clear_filter'])) {
            $filters->reinit();
        } else {
            if (isset($post['max_amount'])) {
                $filters->max_amount = null;
            }

            if ((isset($post['nbshow']) && is_numeric($post['nbshow']))
            ) {
                $filters->show = $post['nbshow'];
            }

            if (isset($post['end_date_filter']) || isset($post['start_date_filter'])) {
                try {
                    if (isset($post['start_date_filter'])) {
                        $field = _T("start date filter");
                        $filters->start_date_filter = $post['start_date_filter'];
                    }
                    if (isset($post['end_date_filter'])) {
                        $field = _T("end date filter");
                        $filters->end_date_filter = $post['end_date_filter'];
                    }
                } catch (Exception $e) {
                    $error_detected[] = $e->getMessage();
                }
            }

            if (isset($post['payment_type_filter'])) {
                $ptf = (int)$post['payment_type_filter'];
                $ptypes = new PaymentTypes(
                    $this->zdb,
                    $this->preferences,
                    $this->login
                );
                $ptlist = $ptypes->getList();
                if (isset($ptlist[$ptf])) {
                    $filters->payment_type_filter = $ptf;
                } elseif ($ptf == -1) {
                    $filters->payment_type_filter = null;
                } else {
                    $error_detected[] = _T("- Unknown payment type!");
                }
            }
        }

        $this->session->$type = $filters;

        if (count($error_detected) > 0) {
            //report errors
            foreach ($error_detected as $error) {
                $this->flash->addMessage(
                    'error_detected',
                    $error
                );
            }
        }

        return $response
            ->withStatus(301)
            ->withHeader('Location', $this->router->pathFor('contributions', ['type' => $raw_type]));
    }
)->setName(
    'payments_filter'
)->add($authenticate);

$app->get(
    '/contribution/{type:fee|donation}/{action:add|edit}[/{id:\d+}]',
    function ($request, $response, $args) {
        $action = $args['action'];
        $get = $request->getQueryParams();
        $id_cotis = null;
        $id_adh = null;
        if (isset($args['id'])) {
            $id_cotis = $args['id'];
        }

        if ($action === 'edit' && $id_cotis === null) {
            throw new \RuntimeException(
                _T("Contribution ID cannot ben null calling edit route!")
            );
        } elseif ($action === 'add' && $id_cotis !== null) {
            return $response
                ->withStatus(301)
                ->withHeader(
                    'Location',
                    $this->router->pathFor(
                        'contribution',
                        [
                            'type'      => $args['type'],
                            'action'    => 'add'
                        ]
                    )
                );
        }

        // contribution types
        $ct = new ContributionsTypes($this->zdb);
        $contributions_types = $ct->getList($args['type'] === 'fee');

        if ($this->session->contribution !== null) {
            $contrib = $this->session->contribution;
            $this->session->contribution = null;
        } else {
            if ($args['action'] === 'edit') {
                $contrib = new Contribution($this->zdb, $this->login, (int)$id_cotis);
                $id_adh = $contrib->member;
                if ($contrib->id == '') {
                    //not possible to load contribution, exit
                    $this->flash->addMessage(
                        'error_detected',
                        str_replace(
                            '%id',
                            $id_cotis,
                            _T("Unable to load contribution #%id!")
                        )
                    );
                    return $response
                        ->withStatus(301)
                        ->withHeader('Location', $this->router->pathFor(
                            'contributions',
                            ['type' => 'contributions']
                        ));
                }
            } else {
                $id_type_cotis = 0;
                if (isset($get[ContributionsTypes::PK])
                    && $get[ContributionsTypes::PK]
                    && $action === 'add'
                ) {
                    $id_type_cotis = $get[ContributionsTypes::PK];
                }
                $cparams = ['type' => array_keys($contributions_types)[0]];

                //member id
                $id_adh = null;
                if (isset($get[Adherent::PK]) && $get[Adherent::PK] > 0 && $action === 'add') {
                    $id_adh = (int)$get[Adherent::PK];
                    $cparams['adh'] = $id_adh;
                }

                //transaction id
                $trans_id = null;
                if (isset($get[Transaction::PK]) && $get[Transaction::PK] > 0) {
                    $cparams['trans'] = $get[Transaction::PK];
                }

                $contrib = new Contribution(
                    $this->zdb,
                    $this->login,
                    (count($cparams) > 0 ? $cparams : null)
                );

                if (isset($get['montant_cotis']) && $get['montant_cotis'] > 0 && $action === 'add') {
                    $contrib->amount = $get['montant_cotis'];
                }

                if ($contrib->isTransactionPart()) {
                    $id_adh = $contrib->member;
                }
            }
        }

        $disabled = array();

        if (!is_int($contrib->id)) {
            // initialiser la structure contribution à vide (nouvelle contribution)
            $contribution['duree_mois_cotis'] = $this->preferences->pref_membership_ext;
        }

        // template variable declaration
        $title = null;
        if ($args['type'] === 'fee') {
            $title = _T("Membership fee");
        } else {
            $title = _T("Donation");
        }

        if ($contrib->id != '') {
            $title .= ' (' . _T("modification") . ')';
        } else {
            $title .= ' (' . _T("creation") . ')';
        }

        // required fields
        $required = [
            'id_type_cotis'     => 1,
            'id_adh'            => 1,
            'date_enreg'        => 1,
            'date_debut_cotis'  => 1,
            'date_fin_cotis'    => $contrib->isCotis(),
            'montant_cotis'     => $contrib->isCotis() ? 1 : 0
        ];

        $params = [
            'page_title'        => $title,
            'required'          => $required,
            'disabled'          => $disabled,
            'contribution'      => $contrib,
            'adh_selected'      => $id_adh,
            'require_calendar'  => true,
            'type'              => $args['type']
        ];

        // contribution types
        $params['type_cotis_options'] = $contributions_types;

        // members
        $members = [];
        $m = new Members();
        $required_fields = array(
            'id_adh',
            'nom_adh',
            'prenom_adh'
        );
        $list_members = $m->getList(false, $required_fields);

        if (count($list_members) > 0) {
            foreach ($list_members as $member) {
                $pk = Adherent::PK;
                $sname = mb_strtoupper($member->nom_adh, 'UTF-8') .
                    ' ' . ucwords(mb_strtolower($member->prenom_adh, 'UTF-8')) .
                    ' (' . $member->id_adh . ')';
                $members[$member->$pk] = $sname;
            }
        }

        $params['members'] = [
            'filters'   => $m->getFilters(),
            'count'     => $m->getCount()
        ];

        //check if current attached member is part of the list
        if (isset($contrib)
            && $contrib->member > 0
            && !isset($members[$contrib->member])
        ) {
            $members[$contrib->member] = Adherent::getSName($this->zdb, $contrib->member, true);
        }

        if (count($members)) {
            $params['members']['list'] = $members;
        }

        $ext_membership = '';
        if (isset($contrib) && $contrib->isCotis() || !isset($contrib) && $args['type'] === 'fee') {
            $ext_membership = $this->preferences->pref_membership_ext;
        }
        $params['pref_membership_ext'] = $ext_membership;
        $params['autocomplete'] = true;

        // display page
        $this->view->render(
            $response,
            'ajouter_contribution.tpl',
            $params
        );
        return $response;
    }
)->setName('contribution')->add($authenticate);

$app->post(
    '/contribution/{type:fee|donation}/{action:add|edit}[/{id:\d+}]',
    function ($request, $response, $args) {
        $post = $request->getParsedBody();

        if (isset($post['btnreload'])) {
            $redirect_url = $this->router->pathFor('contribution', $args);
            $redirect_url .= '?' . Adherent::PK . '=' . $post[Adherent::PK] . '&' .
                ContributionsTypes::PK . '=' . $post[ContributionsTypes::PK] . '&' .
                'montant_cotis=' . $post['montant_cotis'];
            return $response
                ->withStatus(301)
                ->withHeader('Location', $redirect_url);
        }

        $error_detected = [];
        $warning_detected = [];
        $redirect_url = null;

        $action = $args['action'];
        $id_cotis = null;
        if (isset($args['id'])) {
            $id_cotis = $args['id'];
        }

        $id_adh = $post['id_adh'];

        if ($action === 'edit' && $id_cotis === null) {
            throw new \RuntimeException(
                _T("Contribution ID cannot ben null calling edit route!")
            );
        } elseif ($action === 'add' && $id_cotis !== null) {
            throw new \RuntimeException(
                _T("Contribution ID must be null calling add route!")
            );
        }

        if ($this->session->contribution !== null) {
            $contrib = $this->session->contribution;
            $this->session->contribution = null;
        } else {
            if ($id_cotis === null) {
                $contrib = new Contribution($this->zdb, $this->login);
            } else {
                $contrib = new Contribution($this->zdb, $this->login, (int)$id_cotis);
            }
        }

        // flagging required fields for first step only
        $required = [
            'id_type_cotis'     => 1,
            'id_adh'            => 1,
            'date_enreg'        => 1,
            'montant_cotis'     => 1, //TODO: not always required, see #196
            'date_debut_cotis'  => 1,
            'date_fin_cotis'    => ($args['type'] === 'fee')
        ];
        $disabled = [];

        // regular fields
        $valid = $contrib->check($post, $required, $disabled);
        if ($valid !== true) {
            $error_detected = array_merge($error_detected, $valid);
        }

        if (count($error_detected) == 0) {
            //all goes well, we can proceed
            $new = false;
            if ($contrib->id == '') {
                $new = true;
            }

            if (count($error_detected) == 0) {
                $store = $contrib->store();
                if ($store === true) {
                    //contribution has been stored :)
                    if ($new) {
                        //if an external script has been configured, we call it
                        if ($this->preferences->pref_new_contrib_script) {
                            $es = new Galette\IO\ExternalScript($this->preferences);
                            $res = $contrib->executePostScript($es);

                            if ($res !== true) {
                                //send admin a mail with all details
                                if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) {
                                    $mail = new GaletteMail($this->preferences);
                                    $mail->setSubject(
                                        _T("Post contribution script failed")
                                    );

                                    $recipients = [];
                                    foreach ($this->preferences->vpref_email_newadh as $pref_email) {
                                        $recipients[$pref_email] = $pref_email;
                                    }
                                    $mail->setRecipients($recipients);

                                    $message = _T("The configured post contribution script has failed.");
                                    $message .= "\n" . _T("You can find contribution information and script output below.");
                                    $message .= "\n\n";
                                    $message .= $res;

                                    $mail->setMessage($message);
                                    $sent = $mail->send();

                                    if (!$sent) {
                                        $txt = preg_replace(
                                            array('/%name/', '/%email/'),
                                            array($adh->sname, $adh->getEmail()),
                                            _T("A problem happened while sending to admin post contribution notification for user %name (%email) contribution")
                                        );
                                        $this->history->add($txt);
                                        $error_detected[] = $txt;
                                        //Mails are disabled... We log (not safe, but)...
                                        Analog::log(
                                            'Post contribution script has failed. Here was the data: ' .
                                            "\n" . print_r($res, true),
                                            Analog::ERROR
                                        );
                                    }
                                } else {
                                    //Mails are disabled... We log (not safe, but)...
                                    Analog::log(
                                        'Post contribution script has failed. Here was the data: ' .
                                        "\n" . print_r($res, true),
                                        Analog::ERROR
                                    );
                                }
                            }
                        }
                    }
                } else {
                    //something went wrong :'(
                    $error_detected[] = _T("An error occurred while storing the contribution.");
                }
            }
        }

        if (count($error_detected) == 0) {
            // Get member informations
            $adh = new Adherent($this->zdb);
            $adh->load($contrib->member);

            if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) {
                $texts = new Texts(
                    $this->texts_fields,
                    $this->preferences,
                    $this->router,
                    array(
                        'name_adh'          => custom_html_entity_decode($adh->sname),
                        'firstname_adh'     => custom_html_entity_decode($adh->surname),
                        'lastname_adh'      => custom_html_entity_decode($adh->name),
                        'mail_adh'          => custom_html_entity_decode($adh->getEmail()),
                        'login_adh'         => custom_html_entity_decode($adh->login),
                        'deadline'          => custom_html_entity_decode($contrib->end_date),
                        'contrib_info'      => custom_html_entity_decode($contrib->info),
                        'contrib_amount'    => custom_html_entity_decode($contrib->amount),
                        'contrib_type'      => custom_html_entity_decode($contrib->type->libelle)
                    )
                );
                if ($new && isset($_POST['mail_confirm'])
                    && $_POST['mail_confirm'] == '1'
                ) {
                    if (GaletteMail::isValidEmail($adh->getEmail())) {
                        $text = 'contrib';
                        if (!$contrib->isCotis()) {
                            $text = 'donation';
                        }
                        $mtxt = $texts->getTexts($text, $adh->language);

                        $mail = new GaletteMail($this->preferences);
                        $mail->setSubject($texts->getSubject());
                        $mail->setRecipients(
                            array(
                                $adh->getEmail() => $adh->sname
                            )
                        );

                        $mail->setMessage($texts->getBody());
                        $sent = $mail->send();

                        if ($sent) {
                            $this->history->add(
                                preg_replace(
                                    array('/%name/', '/%email/'),
                                    array($adh->sname, $adh->getEmail()),
                                    _T("Mail sent to user %name (%email)")
                                )
                            );
                        } else {
                            $txt = preg_replace(
                                array('/%name/', '/%email/'),
                                array($adh->sname, $adh->getEmail()),
                                _T("A problem happened while sending contribution receipt to user %name (%email)")
                            );
                            $this->history->add($txt);
                            $error_detected[] = $txt;
                        }
                    } else {
                        $txt = preg_replace(
                            array('/%name/', '/%email/'),
                            array($adh->sname, $adh->getEmail()),
                            _T("Trying to send a mail to a member (%name) with an invalid address: %email")
                        );
                        $this->history->add($txt);
                        $warning_detected[] = $txt;
                    }
                }

                // Sent email to admin if pref checked
                if ($new && $this->preferences->pref_bool_mailadh) {
                    // Get email text in database
                    $text = 'newcont';
                    if (!$contrib->isCotis()) {
                        $text = 'newdonation';
                    }
                    $mtxt = $texts->getTexts($text, $this->preferences->pref_lang);

                    $mail = new GaletteMail($this->preferences);
                    $mail->setSubject($texts->getSubject());

                    $recipients = [];
                    foreach ($this->preferences->vpref_email_newadh as $pref_email) {
                        $recipients[$pref_email] = $pref_email;
                    }
                    $mail->setRecipients($recipients);

                    $mail->setMessage($texts->getBody());
                    $sent = $mail->send();

                    if ($sent) {
                        $this->history->add(
                            preg_replace(
                                array('/%name/', '/%email/'),
                                array($adh->sname, $adh->getEmail()),
                                _T("Mail sent to admin for user %name (%email)")
                            )
                        );
                    } else {
                        $txt = preg_replace(
                            array('/%name/', '/%email/'),
                            array($adh->sname, $adh->getEmail()),
                            _T("A problem happened while sending to admin notification for user %name (%email) contribution")
                        );
                        $this->history->add($txt);
                        $error_detected[] = $txt;
                    }
                }
            }

            if (count($warning_detected) > 0) {
                foreach ($warning_detected as $warning) {
                    $this->flash->addMessage(
                        'warning_detected',
                        $warning
                    );
                }
            }

            if (count($error_detected) == 0) {
                if ($contrib->isTransactionPart() && $contrib->transaction->getMissingAmount() > 0) {
                    $redirect_url = $this->router->pathFor(
                        'contribution',
                        [
                            'action'    => 'add',
                            'type'      => $post['contrib_type']
                        ]
                    ) . '?' . Transaction::PK . '=' . $contrib->transaction->id .
                    '&' . Adherent::PK . '=' . $contrib->member;
                } else {
                    $redirect_url = $this->router->pathFor(
                        'contributions',
                        [
                            'type'      => 'contributions'
                        ]
                    ) . '?' . Adherent::PK . '=' . $contrib->member;
                }
            }
        }

        /* TODO: remove */
        if (!isset($contribution['duree_mois_cotis'])
            || $contribution['duree_mois_cotis'] == ''
        ) {
            // On error restore entered value or default to display the form again
            if (isset($_POST['duree_mois_cotis'])
                && $_POST['duree_mois_cotis'] != ''
            ) {
                $contribution['duree_mois_cotis'] = $_POST['duree_mois_cotis'];
            } else {
                $contribution['duree_mois_cotis'] = $this->preferences->pref_membership_ext;
            }
        }

        if (count($error_detected) > 0) {
            //something went wrong.
            //store entity in session
            $this->session->contribution = $contrib;
            $redirect_url = $this->router->pathFor('contribution', $args);

            //report errors
            foreach ($error_detected as $error) {
                $this->flash->addMessage(
                    'error_detected',
                    $error
                );
            }
        } else {
            $this->session->contribution = null;
            if ($redirect_url === null) {
                $redirect_url = $this->router->pathFor('contributions', ['type' => $args['type']]);
            }
        }

        //redirect to calling action
        return $response
            ->withStatus(301)
            ->withHeader('Location', $redirect_url);
    }
)->setName('contribution')->add($authenticate);

$app->get(
    '/transaction/{action:add|edit}[/{id:\d+}]',
    function ($request, $response, $args) {
        $trans = null;

        if ($this->session->transaction !== null) {
            $trans = $this->session->transaction;
            $this->session->transaction = null;
        } else {
            $trans = new Transaction($this->zdb, $this->login);
        }

        $action = $args['action'];
        $trans_id = null;
        if (isset($args['id'])) {
            $trans_id = $args['id'];
        }

        if ($action === 'edit' && $trans_id === null) {
            throw new \RuntimeException(
                _T("Transaction ID cannot ben null calling edit route!")
            );
        } elseif ($action === 'add' && $trans_id !== null) {
             return $response
                ->withStatus(301)
                ->withHeader('Location', $this->router->pathFor('transaction', ['action' => 'add']));
        }

        $transaction['trans_id'] = $trans_id;
        $transaction['trans_amount'] = get_numeric_form_value("trans_amount", '');
        $transaction['trans_date'] = get_form_value("trans_date", '');
        $transaction['trans_desc'] = get_form_value("trans_desc", '');
        $transaction['id_adh'] = get_numeric_form_value("id_adh", '');

        // flagging required fields
        $required = array(
            'trans_amount'  =>  1,
            'trans_date'    =>  1,
            'trans_desc'    =>  1,
            'id_adh'        =>  1
        );
        $disabled = array();

        if ($action === 'edit') {
            // initialize transactions structure with database values
            $trans->load($trans_id);
            if ($trans->id == '') {
                //not possible to load transaction, exit
                throw new \RuntimeException('Transaction does not exists!');
            }
        }

        // template variable declaration
        $title = _T("Transaction");
        if ($action === 'edit') {
            $title .= ' (' . _T("modification") . ')';
        } else {
            $title .= ' (' . _T("creation") . ')';
        }

        $params = [
            'page_title'        => $title,
            'required'          => $required,
            'data'              => $transaction, //TODO: remove
            'transaction'       => $trans,
            'require_calendar'  => true,
            'require_dialog'    => true
        ];

        if ($trans->id != '') {
            $contribs = new Contributions($this->zdb, $this->login);
            $params['contribs'] = $contribs->getListFromTransaction($trans->id);
        }

        // members
        $members = [];
        $m = new Members();
        $required_fields = array(
            'id_adh',
            'nom_adh',
            'prenom_adh'
        );
        $list_members = $m->getList(false, $required_fields);

        if (count($list_members) > 0) {
            foreach ($list_members as $member) {
                $pk = Adherent::PK;
                $sname = mb_strtoupper($member->nom_adh, 'UTF-8') .
                    ' ' . ucwords(mb_strtolower($member->prenom_adh, 'UTF-8')) .
                    ' (' . $member->id_adh . ')';
                $members[$member->$pk] = $sname;
            }
        }

        $params['members'] = [
            'filters'   => $m->getFilters(),
            'count'     => $m->getCount()
        ];
        $params['autocomplete'] = true;

        //check if current attached member is part of the list
        if (isset($trans) && $trans->member > 0) {
            if (!isset($members[$trans->member])) {
                $members =
                    [$trans->member => Adherent::getSName($this->zdb, $trans->member, true)] +
                    $members
                ;
            }
        }

        if (count($members)) {
            $params['members']['list'] = $members;
        }

        // display page
        $this->view->render(
            $response,
            'ajouter_transaction.tpl',
            $params
        );
        return $response;
    }
)->setName('transaction')->add($authenticate);

$app->get(
    '/transaction/{id}/attach/{cid}',
    function ($request, $response, $args) {
        if (!Contribution::setTransactionPart($this->zdb, $args['id'], $args['cid'])) {
            $this->flash->addMessage(
                'error_detected',
                _T("Unable to attach contribution to transaction")
            );
        } else {
            $this->flash->addMessage(
                'success_detected',
                _T("Contribution has been successfully attached to current transaction")
            );
        }

        return $response
            ->withStatus(301)
            ->withHeader('Location', $this->router->pathFor(
                'transaction',
                ['action' => 'edit', 'id' => $args['id']]
            ));
    }
)->setName('attach_contribution')->add($authenticate);

$app->get(
    '/transaction/{id}/detach/{cid}',
    function ($request, $response, $args) {
        if (!Contribution::unsetTransactionPart($this->zdb, $this->login, $args['id'], $args['cid'])) {
            $this->flash->addMessage(
                'error_detected',
                _T("Unable to detach contribution from transaction")
            );
        } else {
            $this->flash->addMessage(
                'success_detected',
                _T("Contribution has been successfully detached from current transaction")
            );
        }

        return $response
            ->withStatus(301)
            ->withHeader('Location', $this->router->pathFor(
                'transaction',
                ['action' => 'edit', 'id' => $args['id']]
            ));
    }
)->setName('detach_contribution')->add($authenticate);

$app->post(
    '/transaction/{action:add|edit}[/{id:\d+}]',
    function ($request, $response, $args) {
        $post = $request->getParsedBody();
        $trans = new Transaction($this->zdb, $this->login);

        $action = $args['action'];
        $trans_id = null;
        if (isset($args['id'])) {
            $trans_id = $args['id'];
        }

        if ($action === 'edit' && $trans_id === null) {
            throw new \RuntimeException(
                _T("Transaction ID cannot ben null calling edit route!")
            );
        } elseif ($action === 'add' && $trans_id !== null) {
            throw new \RuntimeException(
                _T("Transaction ID cannot ben set while adding!")
            );
        }

        $transaction['trans_id'] = $trans_id;
        $transaction['trans_amount'] = $post['trans_amount'];
        $transaction['trans_date'] = $post['trans_date'];
        $transaction['trans_desc'] = $post['trans_desc'];
        $transaction['id_adh'] = $post['id_adh'];

        // flagging required fields
        $required = array(
            'trans_amount'  =>  1,
            'trans_date'    =>  1,
            'trans_desc'    =>  1,
            'id_adh'        =>  1
        );
        $disabled = array();

        if ($action === 'edit') {
            // initialize transactions structure with database values
            $trans->load($trans_id);
            if ($trans->id == '') {
                //not possible to load transaction, exit
                throw new \RuntimeException('Transaction does not exists!');
            }
        }

        $error_detected = [];
        // regular fields
        $valid = $trans->check($_POST, $required, $disabled);
        if ($valid !== true) {
            $error_detected = array_merge($error_detected, $valid);
        }

        if (count($error_detected) == 0) {
            //all goes well, we can proceed
            $new = false;
            if ($trans->id == '') {
                $new = true;
            }

            $store = $trans->store($this->history);
            if ($store === true) {
                //transaction has been stored :)
                if ($new) {
                    $transaction['trans_id'] = $trans->id;
                }
            } else {
                //something went wrong :'(
                $error_detected[] = _T("An error occurred while storing the transaction.");
            }
        }

        if (count($error_detected) == 0) {
            if ($trans->getMissingAmount() > 0) {
                $rparams = [
                    'action'    => 'add',
                    'type'      => $post['contrib_type']
                ];

                if (isset($trans->member)) {
                    $rparams['id_adh'] = $trans->member;
                }

                return $response
                    ->withStatus(301)
                    ->withHeader(
                        'Location',
                        $this->router->pathFor(
                            'contribution',
                            $rparams
                        ) . '?' . Transaction::PK . '=' . $trans->id .
                            '&' . Adherent::PK . '=' . $trans->member
                    );
            } else {
                //report success
                $this->flash->addMessage(
                    'success_detected',
                    _T("Transaction has been successfully stored")
                );

                //get back to transactions list
                return $response
                    ->withStatus(301)
                    ->withHeader(
                        'Location',
                        $this->router->pathFor('contributions', ['type' => 'transactions'])
                    );
            }
        } else {
            //something went wrong.
            //store entity in session
            $this->session->transaction = $trans;

            //report errors
            foreach ($error_detected as $error) {
                $this->flash->addMessage(
                    'error_detected',
                    $error
                );
            }

            //redirect to calling action
            return $response
                ->withStatus(301)
                ->withHeader('Location', $this->router->pathFor('transaction', $args));
        }

        return $response
            ->withStatus(301)
            ->withHeader('Location', $this->router->pathFor('contributions', ['type' => 'transactions']));
    }
)->setName('doEditTransaction')->add($authenticate);

$app->get(
    '/{type:contributions|transactions}/remove' .'/{id:\d+}',
    function ($request, $response, $args) {
        $raw_type = null;
        switch ($args['type']) {
            case 'transactions':
                $raw_type = 'transactions';
                break;
            case 'contributions':
                $raw_type = 'contributions';
                break;
        }

        $data = [
            'id'            => $args['id'],
            'redirect_uri'  => $this->router->pathFor('contributions', ['type' => $args['type']])
        ];

        // display page
        $this->view->render(
            $response,
            'confirm_removal.tpl',
            array(
                'type'          => ($raw_type === 'contributions') ? _T('Contributions') : _T('Transactions'),
                'mode'          => $request->isXhr() ? 'ajax' : '',
                'page_title'    => sprintf(
                    _T('Remove %1$s #%2$s'),
                    ($raw_type === 'contributions') ? _T('contribution') : _T('transaction'),
                    $args['id']
                ),
                'form_url'      => $this->router->pathFor(
                    'doRemoveContribution',
                    ['id' => $args['id'], 'type' => $args['type']]
                ),
                'cancel_uri'    => $data['redirect_uri'],
                'data'          => $data
            )
        );
        return $response;
    }
)->setName('removeContribution')->add($authenticate);

$app->post(
    '/{type:contributions|transactions}/batch/remove',
    function ($request, $response, $args) {
        $post = $request->getParsedBody();

        $raw_type = null;
        switch ($args['type']) {
            case 'transactions':
                $raw_type = 'transactions';
                break;
            case 'contributions':
                $raw_type = 'contributions';
                break;
        }

        $data = [
            'id'            => $post['contrib_sel'],
            'redirect_uri'  => $this->router->pathFor('contributions', ['type' => $args['type']])
        ];

        // display page
        $this->view->render(
            $response,
            'confirm_removal.tpl',
            array(
                'type'          => ($raw_type === 'contributions') ? _T('Contributions') : _T('Transactions'),
                'mode'          => $request->isXhr() ? 'ajax' : '',
                'page_title'    => sprintf(
                    _T('Remove %1$s'),
                    ($raw_type === 'contributions') ? _T('contributions') : _T('transactions')
                ),
                'message'       => str_replace(
                    '%count',
                    count($data['id']),
                    ($raw_type === 'contributions' ?
                        _T('You are about to remove %count contributions.') :
                        _T('You are about to remove %count transactions.'))
                ),
                'form_url'      => $this->router->pathFor(
                    'doRemoveContribution',
                    ['type' => $args['type']]
                ),
                'cancel_uri'    => $data['redirect_uri'],
                'data'          => $data
            )
        );
        return $response;
    }
)->setName('removeContributions')->add($authenticate);

$app->post(
    '/{type:contributions|transactions}/remove[/{id}]',
    function ($request, $response, $args) {
        $ids = null;
        $post = $request->getParsedBody();
        $ajax = isset($post['ajax']) && $post['ajax'] === 'true';
        $success = false;

        if (!is_array($post['id'])) {
            $ids = (array)$post['id'];
        } else {
            $ids = $post['id'];
        }

        $raw_type = null;
        switch ($args['type']) {
            case 'transactions':
                $raw_type = 'transactions';
                break;
            case 'contributions':
                $raw_type = 'contributions';
                break;
        }

        $uri = isset($post['redirect_uri']) ?
            $post['redirect_uri'] :
            $this->router->pathFor('slash');

        if (!isset($post['confirm'])) {
            $this->flash->addMessage(
                'error_detected',
                _T("Removal has not been confirmed!")
            );
        } else {
            $class = '\\Galette\Repository\\' . ucwords($raw_type);
            $contribs = new $class($this->zdb, $this->login);
            $rm = $contribs->remove($ids, $this->history);
            if ($rm) {
                $msg = null;
                if ($raw_type === 'contributions') {
                    $msg = _T("Contributions(s) has been removed!");
                } else {
                    $msg = _T("Transactions(s) has been removed!");
                }
                $this->flash->addMessage(
                    'success_detected',
                    $msg
                );
                $success = true;
            } else {
                $msg = null;
                if ($raw_type === 'contributions') {
                    $msg = _T("An error occurred trying to remove contributions(s) :(");
                } else {
                    $msg = _T("An error occurred trying to remove transaction(s) :(");
                }
                $this->flash->addMessage(
                    'error_detected',
                    $msg
                );
            }
        }

        if (!$ajax) {
            return $response
                ->withStatus(301)
                ->withHeader('Location', $uri);
        } else {
            return $response->withJson(
                [
                    'success'   => $success
                ]
            );
        }
    }
)->setName('doRemoveContribution')->add($authenticate);

//Contribution PDF
$app->get(
    '/contribution/print/{id:\d+}',
    function ($request, $response, $args) {
        $contribution = new Contribution($this->zdb, $this->login, (int)$args['id']);
        $pdf = new PdfContribution($contribution, $this->zdb, $this->preferences);

        $response = $this->response->withHeader('Content-type', 'application/pdf')
                ->withHeader('Content-Disposition', 'attachment;filename="' . $pdf->getFileName() . '"');
        $response->write($pdf->download());
        return $response;
    }
)->setName('printContribution')->add($authenticate);
