]> git.agnieray.net Git - galette.git/commitdiff
Export contributions as CSV; closes #1581
authorJohan Cwiklinski <johan@x-tnd.be>
Mon, 8 Nov 2021 06:12:24 +0000 (07:12 +0100)
committerJohan Cwiklinski <johan@x-tnd.be>
Mon, 8 Nov 2021 06:16:31 +0000 (07:16 +0100)
galette/includes/routes/contributions.routes.php
galette/lib/Galette/Controllers/Crud/ContributionsController.php
galette/lib/Galette/Controllers/CsvController.php
galette/lib/Galette/Entity/Contribution.php
galette/lib/Galette/Entity/Entitled.php
galette/lib/Galette/IO/ContributionsCsv.php [new file with mode: 0644]
galette/lib/Galette/IO/MembersCsv.php
galette/lib/Galette/Repository/Contributions.php
galette/templates/default/gestion_contributions.tpl

index a0e82ee12a3652c64a47a67a43a4c2fe8ba7ef7d..5fd07c7d4d8ad2722415a0fbb57828c57a926877 100644 (file)
@@ -36,6 +36,7 @@
 
 use Galette\Controllers\GaletteController;
 use Galette\Controllers\Crud;
+use Galette\Controllers\CsvController;
 use Galette\Controllers\PdfController;
 use Galette\Entity\Contribution;
 
@@ -69,6 +70,19 @@ $app->post(
     [Crud\ContributionsController::class, 'doEdit']
 )->setName('doEditContribution')->add($authenticate);
 
+//Batch actions on contributions list
+$app->post(
+    '/{type:contributions|transactions}/batch',
+    [Crud\ContributionsController::class, 'handleBatch']
+)->setName('batch-contributionslist')->add($authenticate);
+
+//contributions list CSV export
+$app->map(
+    ['GET', 'POST'],
+    '/{type:contributions|transactions}/export/csv',
+    [CsvController::class, 'contributionsExport']
+)->setName('csv-contributionslist')->add($authenticate);
+
 $app->get(
     '/transaction/add',
     [Crud\TransactionsController::class, 'add']
@@ -104,7 +118,7 @@ $app->get(
     [Crud\ContributionsController::class, 'confirmDelete']
 )->setName('removeContribution')->add($authenticate);
 
-$app->post(
+$app->get(
     '/{type:contributions|transactions}/batch/remove',
     [Crud\ContributionsController::class, 'confirmDelete']
 )->setName('removeContributions')->add($authenticate);
index 5786c1b6f057a68f83497b45da7569bfc6b5ed26..78f78ae45714454d9c99f013fbe9a3473c4c9700 100644 (file)
@@ -36,6 +36,7 @@
 
 namespace Galette\Controllers\Crud;
 
+use Galette\Filters\ContributionsList;
 use Throwable;
 use Analog\Analog;
 use Galette\Controllers\CrudController;
@@ -542,30 +543,20 @@ class ContributionsController extends CrudController
      *
      * @param Request     $request  PSR Request
      * @param Response    $response PSR Response
-     * @param string|null $type     Contribution type
+     * @param string|null $type     One of 'transactions' or 'contributions'
      *
      * @return Response
      */
     public function filter(Request $request, Response $response, string $type = null): Response
     {
-        $raw_type = null;
-        switch ($type) {
-            case 'transactions':
-                $raw_type = 'transactions';
-                break;
-            case 'contributions':
-                $raw_type = 'contributions';
-                break;
-        }
-
-        $type = 'filter_' . $raw_type;
+        $filter_name = 'filter_' . $type;
         $post = $request->getParsedBody();
         $error_detected = [];
 
-        if ($this->session->$type !== null) {
-            $filters = $this->session->$type;
+        if ($this->session->$filter_name !== null) {
+            $filters = $this->session->$filter_name;
         } else {
-            $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type) . 'List';
+            $filter_class = '\\Galette\\Filters\\' . ucwords($type) . 'List';
             $filters = new $filter_class();
         }
 
@@ -613,7 +604,7 @@ class ContributionsController extends CrudController
             }
         }
 
-        $this->session->$type = $filters;
+        $this->session->$filter_name = $filters;
 
         if (count($error_detected) > 0) {
             //report errors
@@ -627,7 +618,56 @@ class ContributionsController extends CrudController
 
         return $response
             ->withStatus(301)
-            ->withHeader('Location', $this->router->pathFor('contributions', ['type' => $raw_type]));
+            ->withHeader('Location', $this->router->pathFor('contributions', ['type' => $type]));
+    }
+
+    /**
+     * Batch actions handler
+     *
+     * @param Request  $request  PSR Request
+     * @param Response $response PSR Response
+     * @param string   $type     One of 'transactions' or 'contributions'
+     *
+     * @return Response
+     */
+    public function handleBatch(Request $request, Response $response, string $type): Response
+    {
+        $filter_name = 'filter_' . $type;
+        $post = $request->getParsedBody();
+
+        if (isset($post['contrib_sel'])) {
+            if (isset($this->session->$filter_name)) {
+                $filters = $this->session->$filter_name;
+            } else {
+                $filters = new ContributionsList();
+            }
+
+            $filters->selected = $post['contrib_sel'];
+            $this->session->$filter_name = $filters;
+
+            if (isset($post['csv'])) {
+                return $response
+                    ->withStatus(301)
+                    ->withHeader('Location', $this->router->pathFor('csv-contributionslist', ['type' => $type]));
+            }
+
+            if (isset($post['delete'])) {
+                return $response
+                    ->withStatus(301)
+                    ->withHeader('Location', $this->router->pathFor('removeContributions'));
+            }
+
+            throw new \RuntimeException('Does not know what to batch :(');
+        } else {
+            $this->flash->addMessage(
+                'error_detected',
+                _T("No contribution was selected, please check at least one.")
+            );
+
+            return $response
+                ->withStatus(301)
+                ->withHeader('Location', $this->router->pathFor('contributions', ['type' => $type]));
+        }
     }
 
     // /CRUD - Read
index a534472d4f32891327e0660733dd0fe673ecb2a9..1dc1c5d2a78b6b3a6a1c0167c08cd62b08350a74 100644 (file)
@@ -36,6 +36,8 @@
 
 namespace Galette\Controllers;
 
+use Galette\Filters\ContributionsList;
+use Galette\IO\ContributionsCsv;
 use Slim\Http\Request;
 use Slim\Http\Response;
 use Galette\Entity\ImportModel;
@@ -716,4 +718,39 @@ class CsvController extends AbstractController
 
         return $this->sendResponse($response, $filepath, $filename);
     }
+
+    /**
+     * Contributions CSV exports
+     *
+     * @param Request  $request  PSR Request
+     * @param Response $response PSR Response
+     * @param string   $type     One of 'contributions' or 'transactions'
+     *
+     * @return Response
+     */
+    public function contributionsExport(Request $request, Response $response, string $type): Response
+    {
+        $post = $request->getParsedBody();
+        $get = $request->getQueryParams();
+
+        $session_var = $post['session_var'] ?? $get['session_var'] ?? 'filter_' . $type;
+
+        if (isset($this->session->$session_var)) {
+            $filters = $this->session->$session_var;
+        } else {
+            $filters = new ContributionsList();
+        }
+
+        $csv = new ContributionsCsv(
+            $this->zdb,
+            $this->login,
+            $type
+        );
+        $csv->exportContributions($filters);
+
+        $filepath = $csv->getPath();
+        $filename = $csv->getFileName();
+
+        return $this->sendResponse($response, $filepath, $filename);
+    }
 }
index 9efc1fc887747c3744d80bc099ae6ba5a6671ca5..9c088519addff2a4975de139f0f2af6be8ae85c2 100644 (file)
@@ -144,7 +144,7 @@ class Contribution
          */
         $this->_fields = array(
             'id_cotis'            => array(
-                'label'    => null, //not a field in the form
+                'label'    => _T('Contribution id'), //not a field in the form
                 'propname' => 'id'
             ),
             Adherent::PK          => array(
@@ -168,7 +168,7 @@ class Contribution
                 'propname' => 'info'
             ),
             'date_enreg'          => array(
-                'label'    => null, //not a field in the form
+                'label'    => _T('Date'), //not a field in the form
                 'propname' => 'date'
             ),
             'date_debut_cotis'    => array(
@@ -181,11 +181,11 @@ class Contribution
                 'propname' => 'end_date'
             ),
             Transaction::PK       => array(
-                'label'    => null, //not a field in the form
+                'label'    => _T('Transaction ID'), //not a field in the form
                 'propname' => 'transaction'
             ),
             //this one is not really a field, but is required in some cases...
-            //adding it here make simplier to check required fields
+            //adding it here make more simple to check required fields
             'duree_mois_cotis'    => array(
                 'label'    => _T("Membership extension:"),
                 'propname' => 'extension'
@@ -255,7 +255,7 @@ class Contribution
                 //count days until end of membership date
                 $diff1 = (int)$bdate->diff($edate)->format('%a');
 
-                //count days beetween end of membership date and offered months
+                //count days between end of membership date and offered months
                 $tdate = clone $edate;
                 $tdate->modify('-' . $preferences->pref_membership_offermonths . ' month');
                 $diff2 = (int)$edate->diff($tdate)->format('%a');
@@ -369,7 +369,7 @@ class Contribution
         $this->_begin_date = $r->date_debut_cotis;
         $enddate = $r->date_fin_cotis;
         //do not work with knows bad dates...
-        //the one with BC comes from 0.63/pgsl demo... Why the hell a so
+        //the one with BC comes from 0.63/pgsql demo... Why the hell a so
         //strange date? don't know :(
         if (
             $enddate !== '0000-00-00'
@@ -1231,13 +1231,7 @@ class Contribution
                     }
                     break;
                 case 'spayment_type':
-                    if ($this->_payment_type === null) {
-                        return '-';
-                    }
-
-                    $ptype = new PaymentType($this->zdb, (int)$this->payment_type);
-                    return $ptype->getName();
-
+                    return $this->getPaymentType();
                     break;
                 case 'model':
                     if ($this->_is_cotis === null) {
index 93295e1531dca95b868717b9055400e9a5b317ca..f59a1f88b7ea7909a564a7b4b9748c8eb9de7c48 100644 (file)
@@ -378,7 +378,7 @@ abstract class Entitled
     {
         $res = $this->get($id);
         if ($res === false) {
-            //get() alred logged
+            //get() already logged
             return self::ID_NOT_EXITS;
         };
         $field = $this->flabel;
diff --git a/galette/lib/Galette/IO/ContributionsCsv.php b/galette/lib/Galette/IO/ContributionsCsv.php
new file mode 100644 (file)
index 0000000..a7f02a5
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Contributions CSV exports
+ *
+ * PHP version 5
+ *
+ * Copyright © 2021 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  IO
+ * @package   Galette
+ *
+ * @author    Johan Cwiklinski <johan@x-tnd.be>
+ * @copyright 2019 The Galette Team
+ * @license   http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
+ * @link      http://galette.tuxfamily.org
+ * @since     Available since 0.9.6-dev - 2021-11-07
+ */
+
+namespace Galette\IO;
+
+use DateTime;
+use Galette\Core\Db;
+use Galette\Core\Login;
+use Galette\Core\Authentication;
+use Galette\Entity\Adherent;
+use Galette\Entity\Contribution;
+use Galette\Entity\ContributionsTypes;
+use Galette\Repository\Contributions;
+use Galette\Filters\ContributionsList;
+use Galette\Repository\PaymentTypes;
+
+/**
+ * Contributions CSV exports
+ *
+ * @category  IO
+ * @name      Csv
+ * @package   Galette
+ * @author    Johan Cwiklinski <johan@x-tnd.be>
+ * @copyright 2021 The Galette Team
+ * @license   http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
+ * @link      http://galette.tuxfamily.org
+ * @since     Available since 0.9.6-dev - 2021-11-07
+ */
+
+class ContributionsCsv extends CsvOut
+{
+    private $filename;
+    private $path;
+    private $zdb;
+    private $login;
+    private $members_fields;
+    private $fields_config;
+    private $filters;
+    private $type;
+
+    /**
+     * Default constructor
+     *
+     * @param Db     $zdb   Db instance
+     * @param Login  $login Login instance
+     * @param string $type  One of 'contributions' or 'transactions'
+     */
+    public function __construct(Db $zdb, Login $login, string $type)
+    {
+        $this->filename = 'filtered_' . $type . 'list.csv';
+        $this->path = self::DEFAULT_DIRECTORY . $this->filename;
+        $this->zdb = $zdb;
+        $this->login = $login;
+        $this->type = $type;
+        parent::__construct();
+    }
+
+    /**
+     * Export members CSV
+     *
+     * @param ContributionsList $filters Current filters
+     *
+     * @return void
+     */
+    public function exportContributions(ContributionsList $filters)
+    {
+        $class = '\\Galette\\Entity\\' . ucwords(trim($this->type, 's'));
+        $contrib = new $class($this->zdb, $this->login);
+
+        $fields = $contrib->fields;
+        //not a real data
+        unset($fields['duree_mois_cotis']);
+        $labels = array();
+
+        foreach ($fields as $k => $f) {
+            $label = $f['label'];
+            if (isset($f['cotlabel'])) {
+                $label = $f['cotlabel'] . ' / ' . $label;
+            }
+            $labels[] = $label;
+        }
+
+        $contributions = new Contributions($this->zdb, $this->login, $filters);
+        $contributions_list = $contributions->getArrayList($filters->selected);
+
+        $ptypes = PaymentTypes::getAll();
+        $ctype = new ContributionsTypes($this->zdb);
+
+        foreach ($contributions_list as &$contribution) {
+            if (isset($contribution->type_paiement_cotis)) {
+                //add textual payment type
+                $contribution->type_paiement_cotis = $ptypes[$contribution->type_paiement_cotis];
+            }
+
+            //add textual type
+            $contribution->id_type_cotis = $ctype->getLabel($contribution->id_type_cotis);
+
+            //handle dates
+            if (isset($contribution->date)) {
+                if (
+                    $contribution->date != ''
+                    && $contribution->date != '1901-01-01'
+                ) {
+                    $date = new DateTime($contribution->date);
+                    $contribution->date = $date->format(__("Y-m-d"));
+                } else {
+                    $contribution->date = '';
+                }
+            }
+
+            if (isset($contribution->date_debut_cotis)) {
+                if (
+                    $contribution->date_debut_cotis != ''
+                    && $contribution->date_debut_cotis != '1901-01-01'
+                ) {
+                    $date = new DateTime($contribution->date_debut_cotis);
+                    $contribution->date_debut_cotis = $date->format(__("Y-m-d"));
+                } else {
+                    $contribution->date_debut_cotis = '';
+                }
+            }
+
+            if (isset($contribution->date_fin_cotis)) {
+                if (
+                    $contribution->date_fin_cotis != ''
+                    && $contribution->date_fin_cotis != '1901-01-01'
+                ) {
+                    $date = new DateTime($contribution->date_fin_cotis);
+                    $contribution->date_fin_cotis = $date->format(__("Y-m-d"));
+                } else {
+                    $contribution->date_fin_cotis = '';
+                }
+            }
+
+            //member name
+            if (isset($contribution->{Adherent::PK})) {
+                $contribution->{Adherent::PK} = Adherent::getSName($this->zdb, $contribution->{Adherent::PK});
+            }
+
+            //handle booleans
+            if (isset($member->activite_adh)) {
+                $member->activite_adh
+                    = ($member->activite_adh) ? _T("Yes") : _T("No");
+            }
+            /*if (isset($member->bool_admin_adh)) {
+                $member->bool_admin_adh
+                    = ($member->bool_admin_adh) ? _T("Yes") : _T("No");
+            }
+            if (isset($member->bool_exempt_adh)) {
+                $member->bool_exempt_adh
+                    = ($member->bool_exempt_adh) ? _T("Yes") : _T("No");
+            }
+            if (isset($member->bool_display_info)) {
+                $member->bool_display_info
+                    = ($member->bool_display_info) ? _T("Yes") : _T("No");
+            }*/
+        }
+
+        $fp = fopen($this->path, 'w');
+        if ($fp) {
+            $this->export(
+                $contributions_list,
+                self::DEFAULT_SEPARATOR,
+                self::DEFAULT_QUOTE,
+                $labels,
+                $fp
+            );
+            fclose($fp);
+        }
+    }
+
+    /**
+     * Get file path on disk
+     *
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * Get file name
+     *
+     * @return string
+     */
+    public function getFileName()
+    {
+        return $this->filename;
+    }
+}
index 892f7eec501f4f505acdfb0328f0ea9541db14a2..dbb1d89ba9a7964296cae81edf05d00165d7ee91 100644 (file)
@@ -3,7 +3,7 @@
 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
 /**
- * Memers CSV exports
+ * Members CSV exports
  *
  * PHP version 5
  *
index 85144c9a602ce0f23941900347b5cec1acead04a..49a1d3fdce8baff46c12fbf3fed73b9047537ccb 100644 (file)
@@ -69,6 +69,7 @@ class Contributions
     private $count = null;
 
     private $sum;
+    private $current_selection;
 
     /**
      * Default constructor
@@ -102,6 +103,34 @@ class Contributions
         return $this->getList(true);
     }
 
+    /**
+     * Get contributions list for a specific transaction
+     *
+     * @param array   $ids        an array of members id that has been selected
+     * @param bool    $as_contrib return the results as an array of
+     * @param array   $fields     field(s) name(s) to get. Should be a string or
+     *                            an array. If null, all fields will be
+     *                            returned
+     * @param boolean $count      true if we want to count members
+     *
+     * @return Contribution[]
+     */
+    public function getArrayList(array $ids, bool $as_contrib = false, array $fields = null, bool $count = true)
+    {
+        if (count($ids) < 1) {
+            Analog::log('No contribution selected.', Analog::INFO);
+            return false;
+        }
+
+        $this->current_selection = $ids;
+        $list = $this->getList($as_contrib, $fields, $count);
+        $array_list = [];
+        foreach ($list as $entry) {
+            $array_list[] = $entry;
+        }
+        return $array_list;
+    }
+
     /**
      * Get contributions list
      *
@@ -161,7 +190,8 @@ class Contributions
 
             $select->join(
                 array('p' => PREFIX_DB . Adherent::TABLE),
-                'a.' . Adherent::PK . '= p.' . Adherent::PK
+                'a.' . Adherent::PK . '= p.' . Adherent::PK,
+                array()
             );
 
             $this->buildWhereClause($select);
@@ -326,6 +356,10 @@ class Contributions
                 break;
         }
 
+        if (isset($this->current_selection)) {
+            $select->where->in('a.' . self::PK, $this->current_selection);
+        }
+
         try {
             if ($this->filters->start_date_filter != null) {
                 $d = new \DateTime($this->filters->rstart_date_filter);
index 3827dfe846dc537913967474d548ce90170a500d..8e34f2a968232bc79ca45cf64963d2c0b5fcd501 100644 (file)
@@ -63,7 +63,7 @@
             </div>
         </div>
         </form>
-        <form action="" method="post" id="listform">
+        <form action="{path_for name="batch-contributionslist" data=["type" => "contributions"]}" method="post" id="listform">
         <table class="listing">
             <thead>
                 <tr>
                     <i class="fas fa-trash fa-fw"></i> {_T string="Delete"}
                 </button>
             </li>
+            <li>
+                <button type="submit" id="csv" name="csv">
+                    <i class="fas fa-file-csv fa-fw"></i> {_T string="Export as CSV"}
+                </button>
+            </li>
         </ul>
     {/if}
 {/if}
                 _init_contribs_page();
 
                 {include file="js_removal.tpl"}
-                {include file="js_removal.tpl" selector="#delete" deleteurl="'{path_for name="removeContributions" data=["type" => "contributions"]}'" extra_check="if (!_checkselection()) {ldelim}return false;{rdelim}" extra_data="delete: true, contrib_sel: $('#listform input[type=\"checkbox\"]:checked').map(function(){ return $(this).val(); }).get()" method="POST"}
+                {include file="js_removal.tpl" selector="#delete" deleteurl="'{path_for name="batch-contributionslist" data=["type" => "contributions"]}'" extra_check="if (!_checkselection()) {ldelim}return false;{rdelim}" extra_data="delete: true, contrib_sel: $('#listform input[type=\"checkbox\"]:checked').map(function(){ return $(this).val(); }).get()" method="POST"}
             });
         </script>
 {/block}