Commit 3491b15c authored by Stephane Bouvry's avatar Stephane Bouvry
Browse files

- Système de rappel (grand retard), commande OK

- TODO Ajouter une alert sur les activités sans aucun validateur
parent 536a563a
Loading
Loading
Loading
Loading
Loading
+39 −22
Original line number Diff line number Diff line
@@ -8,6 +8,12 @@
        </pre>
      </div>
    </div>
    <nav>
      <select v-model="includeNonActive" @change="updateActive">
        <option value="on">Affiche les activités non-active</option>
        <option value="off">Activités "Active" uniquement</option>
      </select>
    </nav>
    <article class="card" v-for="entry in entries">
      <h2 class="card-title">
        <span class="profile">
@@ -33,24 +39,25 @@
              <i class="icon-user"></i>
              Validateur(s) impliqué(s) :
            </h4>
            <div class="alert alert-danger" v-if="entry.np1.length == 0"><i class="icon-attention-1"></i>
              Aucun <em>N+1</em> (validateur hors-lot) n'est assigné à ce déclarant, vous pouvez lui en assigner un (ou
              plusieurs) depuis la fiche personne.
            <div class="danger-area" v-if="entry.np1.length == 0"><i class="icon-attention-1"></i>
              Aucun <em>N+1</em> (validateur hors-lot) n'est assigné à ce déclarant.
              <span v-if="entry.url_show">
                <a :href="entry.url_show">
                  Corriger depuis la fiche
                </a>
              </span>
            </div>
            <div v-if="entry.send == true && entry.validators.length == 0" class="alert alert-danger">
            <div v-if="entry.send == true && entry.validators.length == 0" class="danger-area">
              <i class="icon-attention-1"></i>
              Aucun validateur n'est désigné pour valider ces déclarations, rendez-vous dans la fiche activité afin de
              vérifier que des validateurs sont bien désignés.
            </div>
            <div v-else>
              <p class="info-area">
                Les validateurs ci-dessous sont ceux qui sont impliqués personnellement dans les validations en cours.
              </p>
              <ul>
                <li v-for="v in entry.validators"><i class="icon-cube"></i> {{ v }}</li>
                <li v-for="(v, i) in entry.validators"><i class="icon-cube"></i> {{ v.fullname }}</li>
                <li v-for="v in entry.np1">
                  <i class="icon-tag"></i>
                  <strong>{{ v }}</strong>
                  <strong>{{ v.fullname }}</strong>
                  <small> (Validateur Hors-Lot)</small>
                </li>
              </ul>
@@ -60,20 +67,25 @@
              <i class="icon-cubes"></i>
              Activités
            </h4>
            <p class="info-area">
              Liste des activités où <strong>{{ entry.fullname }}</strong> a été identifié comme déclarant.
              Vous pouvez voir les validateurs désignés pour valider les futurs déclarations envoyées.
            </p>
            <article v-for="activity in entry.activities" class="card xs">
<!--            <p class="info-area">-->
<!--              Liste des activités où <strong>{{ entry.fullname }}</strong> a été identifié comme déclarant.-->
<!--              Vous pouvez voir les validateurs désignés pour valider les futurs déclarations envoyées.-->
<!--            </p>-->
            <article v-for="activity in entry.activities" class="card xs activity">
              <h5>
                <span>
                  <i class="icon-cubes"></i>
                  <strong>[{{ activity.acronym }}]</strong>
                  <em>{{ activity.label }}</em>
                </span>
                <a :href="activity.url_timesheet" v-if="activity.url_timesheet">
                  Configurer
                </a>
              </h5>

              <div v-if="activity.validators.prj.length != 0">
                <i class="icon-cube"></i>
                <strong v-for="p in activity.validators.prj" class="cartouche">{{ p }}</strong>
                <strong v-for="p in activity.validators.prj" class="cartouche">{{ p.fullname }}</strong>
              </div>
              <div v-else class="bg-danger">
                <i class="icon-attention-1"></i>
@@ -82,7 +94,7 @@

              <div v-if="activity.validators.sci.length != 0">
                <i class="icon-beaker"></i>
                <strong v-for="p in activity.validators.sci" class="cartouche">{{ p }}</strong>
                <strong v-for="p in activity.validators.sci" class="cartouche">{{ p.fullname }}</strong>
              </div>
              <div v-else class="bg-danger">
                <i class="icon-attention-1"></i>
@@ -91,7 +103,7 @@

              <div v-if="activity.validators.adm.length != 0">
                <i class="icon-book"></i>
                <strong v-for="p in activity.validators.adm" class="cartouche">{{ p }}</strong>
                <strong v-for="p in activity.validators.adm" class="cartouche">{{ p.fullname }}</strong>
              </div>
              <div v-else class="bg-danger">
                <i class="icon-attention-1"></i>
@@ -209,7 +221,8 @@ export default {
    return {
      /** Liste des données (Personne,periode) **/
      entries: [],
      debug: null
      debug: null,
      includeNonActive: 'off'
    }
  },

@@ -224,7 +237,7 @@ export default {
  methods: {

    fetch() {
      this.$http.get(this.url).then(
      this.$http.get(this.url +'?a=' +this.includeNonActive).then(
          ok => {
            this.entries = ok.data.highdelays;
          },
@@ -232,6 +245,10 @@ export default {
            console.log(ko);
          }
      )
    },

    updateActive() {
      this.fetch();
    }
  },

+58 −28
Original line number Diff line number Diff line
@@ -40,9 +40,9 @@ class OscarTimesheetHighDelayCommand extends OscarAdvancedCommandAbstract
    protected function configure()
    {
        $this
            ->setDescription("Relance automatique des feuilles de temps")
            ->addOption("purge", null, InputOption::VALUE_OPTIONAL, "Suppression des données (dev)", false)
            ->addOption("preview", null, InputOption::VALUE_OPTIONAL, "Aperçu (affiche les personnes concernées)", false)
            ->setDescription("Relance automatique des retards importants")
            ->addOption("include-nonactive", null, InputOption::VALUE_NONE, "Inclus les activités non-active")
            ->addOption("send", null, InputOption::VALUE_NONE, "Effectue l'envoi réél des mails")
            ->addOption("processdate", null, InputOption::VALUE_OPTIONAL, "Date d'execution", false)
        ;
    }
@@ -53,22 +53,9 @@ class OscarTimesheetHighDelayCommand extends OscarAdvancedCommandAbstract

        $processArg = $input->getOption('processdate');

        $purge = $input->getOption('purge');
        $preview = $input->getOption('preview') !== false;
        $includeNonActive = $input->getOption('include-nonactive') != false;
        $send = $input->getOption('send') !== false;

        if( $purge === null ){

//            $recalls = $this->getOrganizationService()->getEntityManager()->getRepository(RecallDeclaration::class)->findAll();
//            if($this->ask("Reset complet des procédures de rappel ? (y)")){
//                foreach ($recalls as $r) {
//                    $this->getOrganizationService()->getEntityManager()->remove($r);
//                }
//                $this->getOrganizationService()->getEntityManager()->flush();
//                die();
//            } else {
//                $this->getIO()->writeln("Annulé");
//            }
        }

        if( !$processArg ){
            $period = PeriodInfos::getPeriodInfosObj(date('Y-m'))->prevMonth();
@@ -80,32 +67,75 @@ class OscarTimesheetHighDelayCommand extends OscarAdvancedCommandAbstract
        $force = false;

        $this->getIO()->title("Relance dans retards importants pour les déclarants (avant " . $period->getPeriodLabel() . ")");
        $this->getIO()->writeln("OPTION send : " . ($send ? 'on' : 'off'));
        $this->getIO()->writeln("OPTION include-nonactive : " . ($includeNonActive ? 'on' : 'off'));

        // Liste des personnes avec des retards anciens (Personne => Période)
        $datas = $this->getPersonService()->getPersonsHighDelay($period->getPeriodCode());
        $datas = $this->getPersonService()->getPersonsHighDelay($period->getPeriodCode(), null, $includeNonActive == 'on');

        $subject = '[OSCAR] Déclaration en retard';
        $body = $this->getOscarConfigurationService()->getHighDelayRelance();


        $validatorsStack = [];

        foreach ($datas as $personDt) {
            $this->getIO()->title("Traitement pour " . $personDt['fullname']);
            if( $personDt['require_alert_declarer'] == true ){
                $this->getIO()->writeln("<green> > Envoi : Déclarant</green>");
                $result = $this->getTimesheetService()->recallHighDelayDeclarer($personDt['person_id']);
                $email = $personDt['email'];
                $fullname = $personDt['fullname'];
                $this->getIO()->writeln("<green> \u{2714} Envoi au Déclarant $fullname ($email)</green>");

                if( $send ) {
                    try {
                        $message = $this->getPersonService()->getMailingService()->newMessage($subject);
                        $message->setBody($body);
                        $message->addTo($email);
                        $this->getPersonService()->getMailingService()->send($message);
                    } catch (\Exception $e) {
                        $this->getIO()->error($e->getMessage());
                    }
                }

            } else {
                $this->getIO()->writeln("<info> - pas d'envoi pour le déclarant</info>");
               // $this->getIO()->writeln("<info> x pas d'envoi pour le déclarant</info>");
            }

            if( count($personDt['np1']) == 0 ){
                $this->getIO()->error("\u{26A0} Le déclarant '$fullname' n'a pas de validateur administratif pour ces créneaux Hors-Lot");
            }

            // Il y'a des validateurs à soliciter pour ce déclarant
            if( $personDt['require_alert_validator'] == true ){
                if( count($personDt['validators']) ){
                    foreach ($personDt['validators'] as $i=>$name) {
                        $this->getIO()->writeln("<green> > Envoi validateur : $name</green>");
                    foreach ($personDt['validators'] as $i=>$infosValidator) {
                        $validatorsStack[$i] = $infosValidator;
                    }
                } else {
                    $this->getIO()->error("Pas de validateur pour ce déclarant");
                    $this->getIO()->error("\u{26A0} Pas de validateur pour les déclarations de $fullname, vérifier que ces activités ont des validateurs désignés");
                }
            } else {
                $this->getIO()->writeln("<info> - pas d'envoi pour le validateur</info>");
               // $this->getIO()->writeln("<comment> \u{2716} pas d'envoi pour le validateur (aucune déclaration à valider)</comment>");
            }
        }

        $this->getIO()->title("Envois groupé aux validateurs");

        foreach ($validatorsStack as $i=>$infosValidator) {
            $validatorName = $infosValidator['fullname'];
            $validatorEmail = $infosValidator['email'];

            $this->getIO()->writeln("<green> \u{2714} Envoi validateur : $validatorName ($validatorEmail)</green>");
            if( $send ) {
                $message = $this->getPersonService()->getMailingService()->newMessage($subject);
                $message->setBody($body);
                $message->addTo($validatorEmail);
                $this->getPersonService()->getMailingService()->send($message);
            }
        }

        if( $send == false ){
            $this->getIO()->warning("AUCUN ENVOI EFFECTUE, UTILISEZ --send POUR l'ENVOI EFFECTIF");
        }

        return 0;
    }
+37 −9
Original line number Diff line number Diff line
@@ -669,7 +669,6 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif
        $formatter = new ActivityToJsonFormatter();
        $json = $formatter->format($activity);
        return $this->jsonOutput($json);
        die("TODO : $activity");
    }

    /**
@@ -935,20 +934,49 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif
    public function deleteAction()
    {
        try {
            $projectGrant = $this->getActivityFromRoute();
            $activity = $this->getActivityFromRoute();

            $this->getOscarUserContextService()->check(
                Privileges::ACTIVITY_DELETE,
                $projectGrant
                $activity
            );
            $project = $projectGrant->getProject();
            $this->getLoggerService()->info(sprintf('Suppression de %s - %s', $projectGrant, $projectGrant->getId()));
            $activity_id = $projectGrant->getId();

            $this->getLoggerService()->info(sprintf('Suppression de %s - %s', $activity, $activity->getId()));

            $project = $activity->getProject();

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////
            // Récupération des informations annexes

            // On supprime les créneaux
            $this->getTimesheetService()->removeTimesheetActivity($activity);

            // Suppression des documents
            $documents = $activity->getDocuments();
            if( count($documents) ){

                $documentPathRoot = $this->getOscarConfigurationService()->getDocumentDropLocation();

                /** @var ContractDocument $d */
                foreach ($documents as $d) {
                    $path = $documentPathRoot.'/'.$d->getPath();
                    if( file_exists($path) ){
                        unlink($path);
                    }
                    $this->getLoggerService()->info("Suppression du document '$d'");
                    $this->getLoggerService()->info("Fichier '$path'");
                    $this->getEntityManager()->remove($d);
                }
                $this->getEntityManager()->flush();
            }


            try {
                $this->getActivityService()->searchDelete($activity_id);
                $this->getActivityService()->searchDelete($activity->getId());
            } catch (\Exception $e) {
            }
            $this->getEntityManager()->remove($projectGrant);

            $this->getEntityManager()->remove($activity);
            $this->getEntityManager()->flush();

            if (!$project) {
@@ -958,7 +986,7 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif

                $this->redirect()->toRoute(
                    'project/show',
                    ['id' => $projectGrant->getProject()->getId()]
                    ['id' => $activity->getProject()->getId()]
                );
            }
        } catch (\Exception $e) {
+12 −1
Original line number Diff line number Diff line
@@ -664,11 +664,20 @@ class TimesheetController extends AbstractOscarController
        ];
    }

    /**
     * url(/feuille-de-temps/highdelay)
     * @return array|JsonModel
     * @throws OscarException
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function highDelayAction()
    {
        $this->getOscarUserContextService()->check(Privileges::MAINTENANCE_VALIDATION_MANAGE);

        $format = $this->params()->fromQuery('f', OscarFormatterConst::FORMAT_IO_HTML);
        $includeNonActive = $this->params()->fromQuery('a', 'off');

        if ($this->isAjax() || $format == OscarFormatterConst::FORMAT_IO_JSON) {
            // Critères
            $period = $this->params()->fromQuery('period', date('Y-m'));
@@ -676,9 +685,11 @@ class TimesheetController extends AbstractOscarController
            $datas = $this->baseJsonResponse();

            // Liste des personnes avec des retards anciens (Personne => Période)
            $datas['include_nonactive'] = $includeNonActive == 'on';
            $datas['highdelays'] = $this->getPersonService()->getPersonsHighDelay(
                $period->getPeriodCode(),
                $this->url()
                $this->url(),
                $includeNonActive == 'on'
            );
            return $this->jsonOutput($datas);
        }
+16 −6
Original line number Diff line number Diff line
@@ -500,7 +500,7 @@ class PersonRepository extends EntityRepository implements IConnectedRepository
     * (ATTENTION : Si aucune déclaration n'a été envoyée, il n'y a pas de résultats)
     * @param int $personId
     */
    public function getRepportDeclarationPerson( int $personId )
    public function getRepportDeclarationPerson( int $personId, bool $includenonActive = false )
    {
        $sql = "SELECT
            declarer_id AS declarer_id,
@@ -519,6 +519,7 @@ class PersonRepository extends EntityRepository implements IConnectedRepository

            GROUP BY declarer_id, period
            ORDER BY period";

        $query = $this->getEntityManager()->getConnection()->prepare($sql);

        $result = $query->executeQuery([
@@ -530,7 +531,13 @@ class PersonRepository extends EntityRepository implements IConnectedRepository
        return $datas;
    }

    public function getIdsDeclarersBeforePeriod( string $period ): array
    /**
     * Retourne les ID des déclarants avant la période donnée.
     *
     * @param string $period
     * @return array
     */
    public function getIdsDeclarersBeforePeriod( string $period, bool $includeNonActive = false ): array
    {
        $qb = $this->getEntityManager()->createQueryBuilder()
            ->select('DISTINCT(p.id) id')
@@ -538,19 +545,22 @@ class PersonRepository extends EntityRepository implements IConnectedRepository
            ->innerJoin('p.workPackages', 'wpp')
            ->groupBy('p.id');

        $parametersQuery = [];


        $extract = DateTimeUtils::periodBounds($period);
        $end = $extract['end'];

        $qb->innerJoin('wpp.workPackage', 'wp')
            ->innerJoin('wp.activity', 'a')
            ->where('a.dateStart < :periodEnd');

        $parametersQuery = [
            'periodEnd' => $end
        ];

        if( $includeNonActive == false ){
            $qb->andWhere('a.status = :status');
            $parametersQuery['status'] = Activity::STATUS_ACTIVE;
        }

        $qb->setParameters($parametersQuery);

        $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
Loading