Commit f63d9a41 authored by Stephane Bouvry's avatar Stephane Bouvry
Browse files

Gestion des options de récupération dynamique (en cours)

parent 675063d2
Loading
Loading
Loading
Loading
Loading
+35 −2
Changes for config/module.config.php: 35 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -53,15 +53,48 @@ return [
    'unicaen-signature' => [

        'get_recipients_methods' => [
            [
                'key' => 'persons_by_role',
                'label' => 'Personnes par rôle',
                'description' => 'Selectionne les personnes en fonction de leurs rôles',
                'options' => [
                    [
                        'key' => 'role_person_id',
                        'label' => "Rôle dans l'activité",
                        'type' => 'checkbox',
                        'values' => [
                            ['id' => 7, 'label' => 'Responsable scientifique'],
                            ['id' => 9, 'label' => 'Chargé de projet']
                        ]
                    ],
                    [
                        'key' => 'role_organisation_id',
                        'label' => "étendre aux structures",
                        'type' => 'checkbox',
                        'values' => [
                            ['id' => 3, 'label' => 'Laboratoire'],
                            ['id' => 5, 'label' => 'Composante de gestion']
                        ]
                    ]
                ],
                'methods_on_create' => null
            ],

            [
                'key' => \UnicaenSignature\Utils\SignatureConstants::GET_RECIPIENTS_METHOD_FIXED,
                'label' => 'Destinataires fixes',
                'methods' => 'fixed'
                'description' => 'Destinataires nommés explicitement',
                'methods' => 'fixed',
                'options' => [],
                'methods_on_create' => null
            ],
            [
                'key' => \UnicaenSignature\Utils\SignatureConstants::GET_RECIPIENTS_METHOD_FREE,
                'label' => 'Destinataires libres',
                'methods' => 'free'
                'description' => 'Destinataires nommés au déclenchement du processus',
                'methods' => 'free',
                'options' => [],
                'methods_on_create' => null
            ]
        ],

+301 −40
Changes for public/src/views/SignatureFlows.vue: 301 added lines, 40 removed lines.
Original line number Diff line number Diff line
<template>
  <section class="signatureflows">
    <div class="alert alert-danger" v-if="error">

    <div class="overlay error" v-if="error">
      <div class="overlay-content">
        {{ error }}
        <hr>
      <a href="#" @click="error = ''">Fermer</a>
        <a href="#" class="btn btn-default" @click="error = ''">Fermer</a>
      </div>
    </div>

    <div class="overlay" v-if="loading">
      <div class="overlay-content">
        <i class="icon-spinner animate-spin"></i>
        {{ loading }}
      </div>
    </div>

    <section class="row">
      <div class="col-md-6">
      <div class="col-md-8">
        <article v-if="editSignatureFlow" class="signatureflows-edit">
          <h2>Edition de la procédure</h2>
          <div>
            <label for="label">
              Intitulé
            </label>
            <input type="text" placeholder="Intitulé" name="label" id="label" class="form-control" />
            <input type="text" v-model="editSignatureFlow.label" placeholder="Intitulé" name="label" id="label"
                   class="form-control"/>
          </div>
          <div>
            <label for="description">
              Description
            </label>
            <input type="text" placeholder="Intitulé" name="description" id="description" class="form-control" />
            <input type="text" v-model="editSignatureFlow.description" placeholder="Une description" name="description"
                   id="description" class="form-control"/>
          </div>
          <section class="steps">
            <h3>étapes</h3>

            <article v-if="editStep" class="step-edit">
              <h3>
                Modification de l'étape
                <strong>{{ editStep.label }}</strong>
              </h3>
              <input type="text" v-model="editStep.label" class="form-control"/>

              <div>
                ORDER : {{ editStep.order }}
              </div>

              <label for="letterfile">Parapheur</label>
              <select v-model="editStep.letterfile" id="letterfile" class="form-control">
                <option v-for="l in letterfiles" :value="l.name">{{ l.label }}</option>
@@ -37,37 +50,87 @@

              <label for="level">Type de validation</label>
              <select v-model="editStep.level" v-if="editStep.letterfile" id="level" class="form-control">
                <option v-for="l in getLevel(editStep.letterfile)" :value="l.key">{{ l.label }}</option>
                <option v-for="l in getLevels(editStep.letterfile)" :value="l.key">{{ l.label }}</option>
              </select>

              <label for="method">Méthode d'obtention des destinataires</label>
              <select v-model="editStep.method" id="method" class="form-control">
              <select v-model="editStep.method" id="method" class="form-control" @change="handlerChangeMethod">
                <option v-for="l in get_recipients_methods" :value="l.key">{{ l.label }}</option>
              </select>

              <section class="options" v-if="stepHasOptions(editStep)">
                <h3>Paramètres de récupération des destinataires</h3>
                <section v-for="o in getStepOptions(editStep)">
                  <h4>{{ o.label }}</h4>
                  <p>{{ o.description }}</p>

                  <section v-if="o.type == 'checkbox'">
                    <label :for="o.key +'_'+ id" v-for="v,id in o.values" class="form-control">
                      <input v-model="editStep.options[o.key]" type="checkbox" :value="v.id" :id="o.key +'_'+ id" />
                      {{ v.label }}
                    </label>
                  </section>
                </section>
              </section>

              <nav>
                <button @click="handlerEditStepSave" class="btn btn-success">Enregistré</button>
                <button @click="handlerEditStepCancel" class="btn btn-danger">Annulé</button>
                <button @click="handlerEditStepSave" class="btn btn-success">Terminer</button>
                <button @click="handlerEditStepCancel" class="btn btn-danger">Annuler</button>
              </nav>
            </article>
            <article v-for="s in editSignatureFlow.steps" class="step" v-else>
              {{ s }} - <button class="btn btn-success btn-xs" @click="handlerEditStep(s)">Modifier</button>
              <button class="btn btn-danger btn-xs" @click="handlerDeleteStep(s)">Supprimer</button>
            <section v-else>
              <h3>étapes</h3>
              <article v-for="s in editSignatureFlow.steps" class="step">
                <h4>
                  <i class="icon-location"></i>
                  {{ s.label }}</h4>
                <div>
                  Niveau de validation : <strong>{{ getLevelsLabel(s) }}</strong><br>
                  Parapheur : <strong>{{ getLetterfileLabel(s.letterfile) }}</strong><br>
                  Destinataires : <strong>{{ getMethodLabel(s.method) }}</strong>
                </div>
                <nav class="buttons-bar">
                  <button class="btn btn-default btn-xs" @click="handlerEditStep(s)">Modifier</button>
                  <button class="btn btn-default btn-xs" @click="handlerDeleteStep(s)">Supprimer</button>
                </nav>
              </article>
            <button @click="handlerNewStep" class="btn btn-success btn-xs">Nouvelle étape</button>
              <nav class="buttons-bar">
                <button @click="handlerNewStep" class="btn btn-default">
                  <i class="icon-location"></i>
                  Nouvelle étape
                </button>
              </nav>
              <hr>
              <nav>
              <button @click="handlerEditSignatureFlowSave" class="btn btn-success">Enregistrer</button>
              <button @click="handlerEditSignatureFlowCancel" class="btn btn-success">Annuler</button>
                <button @click="handlerEditSignatureFlowSave" class="btn btn-large btn-success">Enregistrer la procédure</button>
                <button @click="handlerEditSignatureFlowCancel" class="btn btn-danger">Annuler les modifications</button>
              </nav>
            </section>


          </section>
        </article>
        <section v-else>
          <article v-for="sf in signatureflows" class="signatureflow">
            {{ sf }} -
            <button class="btn btn-xs btn-success" @click="handlerEditSignatureFlow(sf)">Modifier</button>
            <button class="btn btn-xs btn-danger" @click="handlerDeleteSignatureFlow(sf)">Supprimer</button>
            <h3>Procédure : <strong>{{ sf.label }}</strong> <small> (ID: {{ sf.id }})</small></h3>
            <p><small>{{ sf.description }}</small></p>
            <section class="steps" v-if="sf.steps.length">
              <article v-for="s in sf.steps" class="step">
                <h4>{{ s.label }}</h4>
                <small>
                  Parapheur : <strong>{{ getLetterfileLabel(s.letterfile) }}</strong> -
                  Niveau de validation : <strong>{{ getLevelsLabel(s) }}</strong> -
                  Destinataires : <strong>{{ getMethodLabel(s.method) }}</strong>
                </small>
              </article>
            </section>
            <section class="alert alert-info" v-else>
              Aucune étape configurée
            </section>
            <nav class="buttons-bar">
              <button class="btn btn-xs" @click="handlerEditSignatureFlow(sf)">Modifier la procédure</button>
              <button class="btn btn-xs" @click="handlerDeleteSignatureFlow(sf)">Supprimer la procédure</button>
            </nav>
          </article>
          <nav>
            <button class="btn btn-success" @click="handlerNewSignatureFlow">
@@ -77,8 +140,9 @@
        </section>
        <hr>
      </div>
      <div class="col-md-6">
        <pre style="font-size: 12px">{{ $data }}</pre>
      <div class="col-md-4">
        <pre style="font-size: 12px">{{ letterfiles }}</pre>
        <pre style="font-size: 12px">{{ signatureflows }}</pre>
      </div>
    </section>

@@ -91,39 +155,53 @@ export default {
  props: {
    entrypoint: {required: true, type: String}
  },

  data() {
    return {
      signatureflows: [],
      letterfiles: [],
      get_recipients_methods: null,
      editSignatureFlow: null,
      editStep: null,
      editedStep: null,
      editSignatureFlow: null,
      editedSignatureFlow: null,
      error: ''
    }
  },

  methods: {
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // PROCESSUS

    handlerEditSignatureFlow(sf) {
      this.editedSignatureFlow = sf;
      this.editSignatureFlow = JSON.parse(JSON.stringify(sf));
    },

    handlerDeleteSignatureFlow(sf) {
      let i = this.signatureflows.indexOf(sf);
      this.signatureflows.splice(i, 1);
      this.remoteDelete(sf);
      // let i = this.signatureflows.indexOf(sf);
      // this.signatureflows.splice(i, 1);
    },

    handlerEditSignatureFlowCancel() {
      this.editedStep = null;
      this.editStep = null;
      this.editSignatureFlow = null;
      this.editedSignatureFlow = null;
    },

    handlerEditSignatureFlowSave() {
      if (this.editedSignatureFlow) {
        let i = this.signatureflows.indexOf(this.editSignatureFlow);
        this.signatureflows[i] = JSON.parse(JSON.stringify(this.editSignatureFlow));
        this.editedSignatureFlow = null;
      } else {
        this.signatureflows.push(JSON.parse(JSON.stringify(this.editSignatureFlow)));
        this.editSignatureFlow = null;
      }
      this.remoteSave(this.editSignatureFlow);
      this.editSignatureFlow = null;
    },

    handlerNewSignatureFlow() {
      this.editSignatureFlow = {
        id: null,
@@ -131,16 +209,19 @@ export default {
        description: "",
        letterfile: "",
        level: "",
        steps: [

        ]
        steps: [],
        options: null
      }
    },

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // ETAPES
    handlerNewStep() {
      let order = this.editSignatureFlow.steps.length;
      let autoOrder = this.editSignatureFlow.steps.length;
      this.editSignatureFlow.steps.push({
        id: null,
        label: "Nouvelle étape",
        order : order,
        order: autoOrder,
        letterfile: null,
        level: null,
        allValid: false,
@@ -160,14 +241,19 @@ export default {
    handlerEditStep(step) {
      this.editStep = JSON.parse(JSON.stringify(step));
      this.editedStep = step;
      let options = this.getStepOptions(step);
      console.log("options", options);

    },
    handlerDeleteStep(step) {
      let i = this.editSignatureFlow.steps.indexOf(step);
      this.editSignatureFlow.steps.splice(i, 1);
    },
    //
    getLevel(letterfileKey){


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Accès aux données
    getLevels(letterfileKey) {
      for (let i = 0; i < this.letterfiles.length; i++) {
        if (this.letterfiles[i].name == letterfileKey) {
          return this.letterfiles[i].levels;
@@ -175,14 +261,137 @@ export default {
      }
      return [];
    },

    getLevelsLabel(step) {
      let levelKey = step.level;
      if( !levelKey ) {
        return 'Non configuré';
      } else {
        let letterfile = step.letterfile;
        if( !letterfile ){
          return 'Pas de parapheur';
        } else {
          let levels = this.getLevels(step.letterfile);
          for (let i = 0; i < levels.length; i++) {
            if (levels[i].key == step.level) {
              return levels[i].label;
            }
          }
          return 'inconnue';
        }
      }
    },

    handlerChangeMethod(evt){
      console.log("Changement de méthode", this.editStep.method);
      let methodConfig = this.getMethodOption(this.editStep.method);
      if( !this.editStep.options || Array.isArray(this.editStep.options) ){
        this.editStep.options = {};
      }
      methodConfig.forEach(opt => {
        if(!this.editStep.options.hasOwnProperty(opt.key)){
          this.editStep.options[opt.key] = [];
        }
      });
      console.log(methodConfig);
    },

    getMethod( methodKey ){
      for ( let i=0; i<this.get_recipients_methods.length; i++) {
        if( this.get_recipients_methods[i].key == methodKey ){
          return this.get_recipients_methods[i];
        }
      }
      return null;
    },

    getMethodLabel( methodKey ){
      let method = this.getMethod(methodKey);
      if( method ){
        return method.label;
      } else {
        return "N.D";
      }
    },

    getStepOptions(step) {
      let method = this.getMethod(step.method);
      if( method ){
        return method.options;
      } else {
        return null;
      }
    },

    stepHasOptions(step) {
      let options = this.getStepOptions(step);
      return options != null;
    },

    getLetterfile( letterFileKey) {
      for ( let i=0; i<this.letterfiles.length; i++) {
        if( this.letterfiles[i].name == letterFileKey ){
          return this.letterfiles[i];
        }
      }
      return null;
    },

    getLetterfileLabel( letterFileKey) {
      let letterfile = this.getLetterfile(letterFileKey);
      if( letterfile ){
        return letterfile.label;
      } else {
        return "N.D";
      }
    },


    fetch() {
      console.log(this.entrypoint);
      this.loading = "Chargement des processus";
      axios.get(this.entrypoint).then(ok => {
        this.signatureflows = ok.data.signatureflows
        this.letterfiles = ok.data.letterfiles
        this.get_recipients_methods = ok.data.get_recipients_methods
        this.loading = "";
      }, ko => {
        this.error = "";
      })
    },

    remoteDelete(signatureflow) {
      this.loading = "Suppression";
      let datas = new FormData();
      datas.append('action', 'delete');
      datas.append('json', JSON.stringify(signatureflow));
      axios.post(this.entrypoint, datas).then(ok => {
        this.loading = "";
        this.fetch();
      }, ko => {
        this.loading = "";
        try {
          this.error = ko.response.data.error;
        } catch (err) {
          this.error = "Une erreur inconnue est survenue côté serveur";
        }
      })
    },

    remoteSave(signatureflow) {
      this.loading = "Enregistement";
      let datas = new FormData();
      datas.append('action', 'save');
      datas.append('json', JSON.stringify(signatureflow));
      axios.post(this.entrypoint, datas).then(ok => {
        this.loading = "";
        this.fetch();
      }, ko => {
        this.loading = "";
        try {
          this.error = ko.response.data.error;
        } catch (err) {
          this.error = "Une erreur inconnue est survenue côté serveur";
        }
      })
    }
  },
@@ -192,15 +401,67 @@ export default {
}
</script>

<style type="text/css">
<style lang="scss">
.signatureflow {
  border: solid #dde2e5 thin;
  padding: .5em 1em;
  margin: 1em 0;
}
.step {

.buttons-bar {
  padding-top: .5em;
  border-top: solid #dde2e5 thin;
}

.steps {
  margin: 1em 0;
  padding: 1em;
  border: solid #dde2e5 thin;
}

.step {
  padding: .5em 1em;
  border: solid #dde2e5 thin;
  border-left: solid #dde2e5 4px;
  margin: 1em 0;
}

.buttons-right {
  text-align: right;
}

.overlay {
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  transition: all .3s;
  background: rgba(255, 255, 255, .9);

  .overlay-content {
    font-size: 1.2em;
    flex: 0 50%;
    padding: 1em 2em;
    border: solid 1px #dae1e5;
    background: #ffffff;
    box-shadow: 0 0 3em rgba(0, 0, 0, .25);
  }
}

.overlay.error {
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background: rgba(0, 10, 0, .9);

  .overlay-content {
    color: white;
    background: rgba(150, 10, 0, 1);
  }
}
</style>
 No newline at end of file
+67 −6
Changes for src/Controller/SignatureController.php: 67 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -3,11 +3,14 @@
namespace UnicaenSignature\Controller;

use Exception;
use Laminas\Http\Request;
use Laminas\Http\Response;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\Mvc\Plugin\FlashMessenger\FlashMessenger;
use Laminas\View\Model\JsonModel;
use Laminas\View\Model\ViewModel;
use UnicaenSignature\Entity\Db\Signature;
use UnicaenSignature\Entity\Db\SignatureFlow;
use UnicaenSignature\Form\SignatureForm;
use UnicaenSignature\Service\SignatureServiceAwareTrait;
use UnicaenSignature\Utils\SignatureConstants;
@@ -22,14 +25,72 @@ class SignatureController extends AbstractActionController
        return $this->getPluginManager()->get('flashmessenger');
    }

    public function signatureflowsAction() :ViewModel {
        return new JsonModel([
    public function signatureflowsAction(): ViewModel|\Laminas\Http\Response
    {
        if ($this->getRequest()->isPost()) {
            $this->getSignatureService()->getLoggerService()->debug("Traitement des données envoyées");
            $posted = $this->getRequest()->getPost()->toArray();
            $action = $posted['action'];
            switch ($action) {
                case 'save' :
                    $datas = json_decode($posted['json'], true);
                    try {
                        $this->getSignatureService()->saveSignatureFlowFromJSON($datas);
                        return new JsonModel(['message' => "ok"]);
                    } catch (Exception $e) {
                        $this->response->setCustomStatusCode(Response::STATUS_CODE_501);
                        return new JsonModel(['error' => $e->getMessage()]);
                    }

                    break;

                case 'delete' :
                    $datas = json_decode($posted['json'], true);
                    try {
                        $this->getSignatureService()->deleteSignatureFlowFromJSON($datas);
                        return new JsonModel(['message' => "ok"]);
                    } catch (Exception $e) {
                        $this->response->setCustomStatusCode(Response::STATUS_CODE_501);
                        return new JsonModel(['error' => $e->getMessage()]);
                    }
                    break;

                default:
                    $this->response->setCustomStatusCode(Response::STATUS_CODE_400);
                    return new JsonModel(['error' => "Mauvaise utilisation de l'API"]);
            }
            $this->getSignatureService()->getLoggerService()->debug(print_r($posted, true));
        }

        // SignatureFlow normalize
        $signatureflows = $this->getSignatureService()->getSignatureFlows(SignatureConstants::FORMAT_ARRAY);
        foreach ($signatureflows as $i=>$sf) {
            foreach ($sf['steps'] as $j=>&$step) {
                $this->getSignatureService()->getLoggerService()->debug("ICI");
                $options = $this->getSignatureService()->getSignatureConfigurationService()->getDefaultMethodOptionsByKey($step['method']);

                $this->getSignatureService()->getLoggerService()->debug(print_r($options,true));
                foreach ($options as $key=>$value) {
                    if( !array_key_exists($key,$step['options']) ){
                        $step['options'][$key] = $value;
                    }
                }
                $signatureflows[$i]['steps'][$j]['options'] = $step['options'];
            }
        }

        return new JsonModel(
            [
                'version' => 1.0,
            'signatureflows' => $this->getSignatureService()->getSignatureFlows(SignatureConstants::FORMAT_ARRAY),
            'letterfiles' => $this->getSignatureService()->getLetterFiles(SignatureConstants::FORMAT_ARRAY),
            'get_recipients_methods' => $this->getSignatureService()->getSignatureConfigurationService()->getRecipientsMethods(),
                'signatureflows' => $signatureflows,
                'letterfiles' => $this->getSignatureService()->getLetterFiles(
                    SignatureConstants::FORMAT_ARRAY
                ),
                'get_recipients_methods' => $this->getSignatureService()->getSignatureConfigurationService(
                )->getRecipientsMethods(),
                'levels' => []
        ]);
            ]
        );
    }

    /**
+36 −2
Changes for src/Entity/Db/SignatureFlow.php: 36 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -5,6 +5,8 @@ namespace UnicaenSignature\Entity\Db;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
use UnicaenSignature\Formatter\IObjectFormattable;
use UnicaenSignature\Formatter\ObjectFormatter;
use UnicaenSignature\Service\SignatureService;
use UnicaenSignature\Utils\SignatureConstants;

@@ -13,7 +15,7 @@ use UnicaenSignature\Utils\SignatureConstants;
 * @ORM\Entity(repositoryClass="UnicaenSignature\Entity\Repository\SignatureFlowRepository")
 * @ORM\Table(name="unicaen_signature_signatureflow")
 */
class SignatureFlow
class SignatureFlow implements IObjectFormattable
{
    /**
     * @ORM\Id
@@ -37,13 +39,14 @@ class SignatureFlow
    /**
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="SignatureFlowStep", mappedBy="signatureFlow", cascade={"persist","remove"})
     * @ORM\OrderBy({"order" = "ASC"})
     */
    protected $steps;

    /**
     * @return int
     */
    public function getId(): ?int
    public function getId(): int
    {
        return $this->id;
    }
@@ -90,6 +93,14 @@ class SignatureFlow
        return $this->steps;
    }

    public function getStepsIndexed() :array {
        $out = [];
        foreach ($this->getSteps() as $step) {
            $out[$step->getId()] = $step;
        }
        return $out;
    }

    /**
     * @param $steps
     */
@@ -98,4 +109,27 @@ class SignatureFlow
        $this->steps = $steps;
        return $this;
    }

    public function toJson(): array
    {
        return $this->toArray();
    }

    public function toArray(): array
    {
        $objectFormatter = new ObjectFormatter();
        return [
            'id' => $this->getId(),
            'label' => $this->getLabel(),
            'description' => $this->getDescription(),
            'steps' => $objectFormatter->formatCollection($this->getSteps()->toArray(), ObjectFormatter::FORMAT_JSON)
        ];
    }

    public function getOptionLabel(): string
    {
        return $this->getLabel();
    }


}
 No newline at end of file
+60 −2

File changed.

Preview size limit exceeded, changes collapsed.

Loading