Commit 7137e43f authored by Stephane Bouvry's avatar Stephane Bouvry
Browse files

Reassignation/Switch des affectations (A tester)

Note : Ajouter une option pour ne pas supprimer l'ancienne personne (mode renforcement)
parent 23fe9fd5
Loading
Loading
Loading
Loading
+181 −73
Changes for front/src/ReplaceStrengthenPerson.vue: 181 added lines, 73 removed lines.
Original line number Diff line number Diff line
<template>
  <div>
    <div class="overlay" v-if="mode != ''">
      <div class="overlay-content" style="overflow: scroll">
      <div class="overlay-content" :style="step == 1 ? 'overflow: visible;' : 'overflow: scroll;'">
        <a href="#" @click.prevent="handlerCancel()" class="overlay-closer">X</a>
        <h1>
          {{ mode }}
          <strong>{{ person.displayname }}</strong>
        </h1>

        <div class="alert-info alert">
          Remplacer <strong>{{ person.displayname }}</strong> par une autre personnes
        <div class="steps-bar">
          <div class="step" :class="{'current': step == 1, 'done': step > 1}">
            Choisir une personne
          </div>
          <div class="step" :class="{'current': step == 2, 'done': step > 2, 'futur': step < 2}">
            Vérifier les affectations
          </div>
          <div class="step" :class="{'current': step == 3, 'futur': step < 3}">
            Finalisation
          </div>
        </div>

        <div class="step-content">

        <div v-if="recapPending">
          <i class="icon-spinner animate-spin"></i>
          {{ recapPending }}
          <!-- Messages -->
          <div class="alert-info alert" v-if="pendingMessage">
            <i class="animate-spin icon-spinner"></i>
            {{ pendingMessage }}
          </div>
        <div v-else-if="errorRecap" class="alert-danger alert">

          <div v-if="errorMessage" class="alert-danger alert">
            <i class="icon-attention-1"></i>
            <strong>Erreur</strong> :
          {{ errorRecap }}
            {{ errorMessage }}
          </div>
        <div v-else>
          <p>Choisissez une personne pour {{ mode }}</p>



          <div v-if="step == 1">
            <div v-if="!replacer_id">
              <p>Choisissez une personne :</p>
              <person-auto-completer @change="handlerSelectPerson" style="z-index: 10"/>
            </div>
          </div>

          <div class="alert alert-info" v-if="step1info">
            {{ step1info }}
          </div>

          <nav class="buttons text-center" v-if="step == 1">
            <button class="btn btn-success" @click="handlerConfirmStep1()" :class="{ 'disabled': !replacer_id }" >
              <i class="icon-spinner animate-spin" v-if="pendingMessage"></i>
              <i class="icon-angle-right" v-else></i>
              Vérifier les affectations
            </button>
          </nav>

          <section class="">

            <!-- <h2>INFOS : {{ recap.infos }}</h2> -->
          <section class="affectations" v-if="affectations">
            <section class="affectations" v-if="step == 2">
              <section>
                <h2>
                  <i class="icon-beaker"></i>
@@ -39,43 +69,41 @@
                  <h4><i class="icon-cubes"></i> Projets</h4>
                  <p v-if="replacer_id">
                    <strong>{{ replacer }}</strong>
                  va être ajouté dans le(s) <strong>{{ affectations.projects.length }}</strong> projet(s) suivant :
                    va être ajouté dans le(s) <strong>{{ projects.length }}</strong> projet(s) suivant :
                  </p>
                  <div class="columns-3">
                    <div class="card xs applyable"
                         @click="handlerSwitchApply(p)"
                       v-for="p in affectations.projects" :class="{ 'apply': p.apply }">
                         v-for="p in projects" :class="{ 'apply': p.apply, 'active': p.active }">
                      <div class="">
                        <strong>
                          <i class="icon-cubes"></i>
                        {{ p.acronym }}
                          {{ p.label }}
                        </strong>
                      <small>{{ p.label }}</small>
                      <span class="badge bg-info on-right">{{ p.activities_count }}</span>
                      </div>
                    HHH
                    <span class="cartouche xs">
                      {{ p.roles.join(', ') }}
                      <span class="cartouche xs" v-for="role in p.roles"
                            :class="{'past obsolete': !role.active, 'success': p.apply}">
                      {{ role.roleId }}
                    </span>
                    </div>
                  </div>
                </section>

              <h3><i class="icon-cube"></i> Activités</h3>

              <div style="columns: 3">
                <div v-for="p in activities" class="card xs applyable"
                <h4><i class="icon-cube"></i> Activités</h4>
                <div class="columns-3">
                  <div class="card xs applyable"
                       @click="handlerSwitchApply(p)"
                     :class="{ 'active': p.active, 'disabled': !p.active, 'apply': p.apply }">
                  <div style="border-bottom: solid thin #ccc">
                       v-for="p in activities" :class="{ 'apply': p.apply, 'active': p.active }">
                    <div class="">
                      <strong>
                        <i class="icon-cube"></i>
                      {{ p.acronym }}
                        {{ p.label }}
                      </strong>
                    <em>{{ p.label }}</em>
                    <i class="on-right" :class="'icon-status-' + p.status" :title="p.status_text"></i>
                    </div>
                  <small>({{ p.roles.join(', ') }})</small>
                    <span class="cartouche xs" v-for="role in p.roles"
                          :class="{'past obsolete': !role.active, 'success': p.apply}">
                      {{ role.roleId }}
                    </span>
                  </div>
                </div>
              </section>
@@ -85,14 +113,15 @@
                  <i class="icon-building-filled"></i>
                  Structure
                </h2>

                <div v-for="p in structures" class="card xs applyable"
                     @click="handlerSwitchApply(p)"
                     :class="{'apply': p.apply }">
                  <strong>{{ p.label }}</strong>
                  <small>({{ p.roles.join(', ') }})</small>
                  <span class="cartouche xs" v-for="role in p.roles"
                        :class="{'past obsolete': !role.active, 'success': p.apply}">
                  {{ role.roleId }}
                </span>
                </div>

              </section>

              <section>
@@ -101,54 +130,105 @@
                  Validation
                </h2>

              <div v-if="affectations.validations.prj">
                <div v-if="validations.prj">
                  <h4><i class="icon-cube"></i>Validation projet</h4>
                  <div class="card xs applyable"
                       @click="v.apply = !v.apply; console.log('OK')"
                     v-for="v in affectations.validations.prj"
                       v-for="v in validations.prj"
                       :class="{'apply': v.apply }">
                    <strong>{{ v.acronym }}</strong>
                    <em>{{ v.label }}</em>
                  </div>
                </div>

              <div v-if="affectations.validations.sci">
                <div v-if="validations.sci">
                  <h4><i class="icon-beaker"></i>Validation scientifique</h4>
                  <div class="card xs applyable"
                       @click="v.apply = !v.apply"
                     v-for="v in affectations.validations.sci"
                       v-for="v in validations.sci"
                       :class="{'apply': v.apply }">
                    <strong>{{ v.acronym }}</strong>
                    <em>{{ v.label }}</em>
                  </div>
                </div>

              <div v-if="affectations.validations.adm">
                <div v-if="validations.adm">
                  <h4><i class="icon-book"></i>Validation administratif</h4>
                  <div class="card xs applyable"
                       @click="v.apply = !v.apply"
                     v-for="v in affectations.validations.adm"
                       v-for="v in validations.adm"
                       :class="{'apply': v.apply }">
                    <strong>{{ v.acronym }}</strong>
                    <em>{{ v.label }}</em>
                  </div>
                </div>
              </section>

              <section>
                <h2>
                  <i class="icon-calendar"></i>
                  Hiérarchie
                </h2>

                <div>
                  <h4><i class="icon-book"></i>Reférent(s)</h4>
                  <p>
                    <i class="icon-info-circled"></i>
                    Personnes qui seront chargées de valider
                    les déclaration <strong>Hors-lot</strong>
                    de {{ person.displayname }}.
                  </p>
                  <div class="card xs person-card" v-for="p in subordinates" :class="{'apply': p.apply}">
                    <div class="card-title">
                      <img :src="'//www.gravatar.com/avatar/' +p.mailmd5 +'&s=20'" alt="" class="gravatar" width="20">
                      <strong>
                        <i class="icon-user"></i>
                        {{ p.label }}
                      </strong>
                    </div>
                    <small>
                      <i class="icon-mail"></i>
                      {{ p.mail }}
                    </small>
                  </div>
                </div>

                <div>
                  <h4><i class="icon-beaker"></i>Subordonné(s)</h4>
                  <p>
                    <i class="icon-info-circled"></i>
                    Personnes pour qui <strong>{{ person.displayname }}</strong> devra
                    valider les déclarations <strong>Hors-lot</strong>
                  </p>
                  <div class="card xs person-card" v-for="p in referents" :class="{'apply': p.apply}">
                    <div class="card-title">
                      <img :src="'//www.gravatar.com/avatar/' +p.mailmd5 +'&s=20'" alt="" class="gravatar" width="20">
                      <strong>
                        <i class="icon-user"></i>
                        {{ p.label }}
                      </strong>
                    </div>
                    <small>
                      <i class="icon-mail"></i>
                      {{ p.mail }}
                    </small>
                  </div>
                </div>
              </section>
          <hr>
          <nav class="buttons text-center">
            <button class="btn btn-success" @click="handlerConfirmReplace()">
              Confirmer

              <nav class="buttons">
                <button class="btn btn-success" @click="handlerConfirmReplace">
                  Terminé
                </button>
              </nav>
            </section>

          </section>
        </div>
      </div>
    </div>

    <div class="replace-strengthen-person actions text-center">
      <div class="alert alert-danger">
        {{ error }}
      </div>
      <button @click="handlerReplace">
        <i class="icon-rewind-outline"></i> Remplacer
      </button>
@@ -161,7 +241,8 @@

<script>
// MANUAL COMPILATION
// node node_modules/.bin/vue-cli-service build --name ReplaceStrengthenPerson --dest ../public/js/oscar/dist/ReplaceStrengthenPerson --no-clean --formats umd,umd-min --target lib src/ReplaceStrengthenPerson.vue;
// node node_modules/.bin/vue-cli-service build --name ReplaceStrengthenPerson --dest ../public/js/oscar/dist/ --no-clean --formats umd,umd-min --target lib src/ReplaceStrengthenPerson.vue

import PersonAutoCompleter from "./components/PersonAutoCompleter";

export default {
@@ -178,10 +259,10 @@ export default {
    urlReplace: {
      required: true
    },
    urlStrengthen: {
    urlAffectation: {
      required: true
    },
    affectations: {
    urlStrengthen: {
      required: true
    }
  },
@@ -193,19 +274,29 @@ export default {
      recapPending: "",
      errorRecap: "",
      error: "",

      step: 1,

      pendingMessage: "",
      errorMessage: "",

      replacer: "",
      replacer_id: null,
      step1info: "",

      projects: {},
      activities: {},
      structures: {},
      validations: {}
      validations: {},
      referents: {},
      subordinates: {}
    }
  },

  methods: {
    handlerReplace() {
      this.mode = 'Remplacer';
      this.loadingMessage = "Chargement de l'aperçu";
    },

    handlerSwitchApply(item) {
@@ -219,17 +310,30 @@ export default {
     * @param e
     */
    handlerSelectPerson(e) {
      console.log("handlerSelectPerson");

      this.replacer = e.displayname;
      this.replacer_id = e.id;
      this.step1info = "Remplacer " + this.person.displayname + " par " + this.replacer;
    },

      this.recap = {
        "replacer_id": e.id,
        "infos": "Remplacer " + this.person.displayname + " par " + e.displayname,
        "dump": this.person
      };
      console.log(e);
    handlerConfirmStep1(){
      this.pendingMessage = "Chargement des affectations...";
      this.$http.get(this.urlAffectation+"?person=" + this.replacer_id).then(
          ok => {
            this.step = 2;
            this.projects = ok.body.affectations.projects;
            this.activities = ok.body.affectations.activities;
            this.structures = ok.body.affectations.structures;
            this.validations = ok.body.affectations.validations;
            this.referents = ok.body.affectations.referents;
            this.subordinates = ok.body.affectations.subordinates;
          },
          ko => {
            this.errorMessage = ko.body;
          }

      ).then( () => {
        this.pendingMessage = "";
      });
    },

    handlerConfirmReplace() {
@@ -239,45 +343,49 @@ export default {
        'activities': this.activities,
        'structures': this.structures,
        'validations': this.validations,
        'subordinates': this.subordinates,
        'referents': this.referents,
        'replacer_id': this.replacer_id
      };
      console.log('OUT', out);
      this.recapPending = "Remplacement...";

      this.pendingMessage = "Remplacement...";
      this.step = 3;

      data.append("out", JSON.stringify(out));

      this.$http.post(this.urlReplace, data).then(
      this.$http.post(this.urlAffectation, data).then(
          ok => {
            document.location.refresh();
            document.location.reload();
          }, ko => {
            if (ko.status == 403) {
              this.errorRecap = "Non-autorisé";
              this.errorMessage = "Non-autorisé";
            } else {
              if (ko.body.error) {
                this.errorRecap = "Erreur : " + ko.body.error;
                this.errorMessage = "Erreur : " + ko.body.error;
              } else {
                this.errorRecap = ko.body;
                this.errorMessage = ko.body;
              }
            }
          }
      ).then( foo => this.recapPending = "");
      ).then( foo => {
        this.pendingMessage = "";
      })
    },

    handlerCancel() {
      this.recapPending = "";
      this.errorRecap = "";
      this.recap = "";
      this.step = 1;
      this.errorMessage = "";
      this.mode = "";
      this.replacer_id = null;
      this.replacer = "";
      this.step1info = "";
    },
  },


  // Lifecycle
  mounted() {
    console.log("mounted");
    this.projects = this.affectations.projects;
    this.activities = this.affectations.activities;
    this.structures = this.affectations.structures;
    this.validations = this.affectations.validations;
    // amazing void content
  }
}
</script>
 No newline at end of file
+1 −1
Changes for module/Oscar/config/module.config.php: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -248,7 +248,7 @@ return array(
                    'privileges' => \Oscar\Provider\Privileges::PERSON_INDEX
                ],
                [ 'controller' =>   'Person',
                    'action' => ['edit', 'new', 'merge', 'organizationRole', 'notificationPerson', 'notificationPersonGenerate'],
                    'action' => ['edit', 'new', 'merge', 'organizationRole', 'notificationPerson', 'notificationPersonGenerate', 'affectation'],
                    'privileges' => \Oscar\Provider\Privileges::PERSON_EDIT
                ],

+7 −0
Changes for module/Oscar/config/routes.yml: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -605,6 +605,13 @@ person:
                defaults:
                    action: access

        affectation:
            type: segment
            options:
                route: '/affectation/:id'
                defaults:
                    action: affectation

        notification:
            type: segment
            options:
+64 −2
Changes for module/Oscar/src/Oscar/Controller/PersonController.php: 64 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -452,8 +452,7 @@ class PersonController extends AbstractOscarController implements UsePersonServi
        if (
            !$this->getOscarUserContextService()->hasPrivilegeDeep(Privileges::ACTIVITY_PERSON_MANAGE) &&
            !$this->getOscarUserContextService()->hasPrivilegeDeep(Privileges::PROJECT_PERSON_MANAGE) &&
            !$this->getOscarUserContextService()->hasPrivilegeDeep(Privileges::ORGANIZATION_EDIT) &&
            !$this->getOscarUserContextService()->hasPrivilegeDeep(Privileges::ACTIVITY_INDEX)
            !$this->getOscarUserContextService()->hasPrivilegeDeep(Privileges::ORGANIZATION_EDIT)
        )
            return $this->getResponseUnauthorized("Vous n'avez pas accès à la liste des personnes");

@@ -902,6 +901,69 @@ class PersonController extends AbstractOscarController implements UsePersonServi
        ];
    }

    /**
     * @param string $idPersonKey
     * @return Person
     * @throws OscarException
     */
    protected function getRoutePerson(string $idPersonKey = 'id'): Person
    {
        $id = $this->params()->fromRoute($idPersonKey);
        return $this->getPersonService()->getPersonById($id, true);
    }

    /**
     *
     */
    public function affectationAction()
    {
        try {
            $person = $this->getRoutePerson();
            $manage = $this->getOscarUserContextService()->hasPrivileges(Privileges::PERSON_EDIT);
            if (!$manage) {
                throw new OscarException("Accès interdit");
            }
            // TRAITEMENT
            if ($this->getRequest()->isPost()) {
                try {
                    $out = $this->params()->fromPost('out', "");
                    $this->getPersonService()->affectationsReplace($person, json_decode($out, true));
                    return $this->getResponseOk("done");
                } catch (\Exception $e) {
                    return $this->getResponseInternalError($e->getMessage());
                }
            }

            // Récupération des affectations
            if ($this->params()->fromQuery('f') == 'json' || $this->isAjax()) {
                try {
                    $replacerId = $this->params()->fromQuery('person');
                    $replacer = $this->getPersonService()->getPersonById($replacerId, true);
                } catch (\Exception $e) {
                    return $this->getResponseInternalError(
                        sprintf(
                            "Impossible de traver le remplaçant : %s",
                            $e->getMessage()
                        )
                    );
                }
                $out = [
                    'affectations' => $this->getPersonService()->getPersonAffectationsArray($person, $replacer)
                ];
                return $this->ajaxResponse($out);
            }

            throw new OscarException("Mauvaise utilisation de l'API");
        } catch (\Exception $e) {
            return $this->getResponseInternalError(
                sprintf(
                    "Gestion des affectations impossible : %s",
                    $e->getMessage()
                )
            );
        }
    }


    public function mergeAction()
    {
+1 −1
Changes for module/Oscar/src/Oscar/Controller/ProjectGrantController.php: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -1946,7 +1946,6 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif
        // Récupération de l'activités
        $activity = $this->getProjectGrantService()->getGrant($this->params()->fromRoute('id'));


        $this->getOscarUserContextService()->check(Privileges::ACTIVITY_PERSON_SHOW, $activity);

        $out = [];
@@ -2010,6 +2009,7 @@ class ProjectGrantController extends AbstractOscarController implements UseNotif
                'context' => $context,
                'urlEdit' => $urlEdit,
                'urlShow' => $urlShow,
                'past' => $activityPerson->isPast(),
                'enroller' => $idEnroller,
                'enrollerLabel' => $activity->getLabel(),
                'editable' => $editable,
Loading