<?php
namespace App\Model\Table;

//use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;
use Cake\I18n\Time;
use Cake\I18n\Date;
use Cake\ORM\Association\BelongsTo;
use Cake\ORM\Entity;

/**
 * Materiels Model
 *
 * @property \Cake\ORM\Association\BelongsTo $SurCategories
 * @property \Cake\ORM\Association\BelongsTo $Categories
 * @property \Cake\ORM\Association\BelongsTo $SousCategories
 * @property \Cake\ORM\Association\BelongsTo $GroupesThematiques
 * @property \Cake\ORM\Association\BelongsTo $GroupesMetiers
 * @property \Cake\ORM\Association\BelongsTo $Projets
 * @property \Cake\ORM\Association\BelongsTo $Organismes
 * @property \Cake\ORM\Association\BelongsTo $Sites
 * @property \Cake\ORM\Association\HasMany $Documents
 * @property \Cake\ORM\Association\HasMany $Emprunts
 * @property \Cake\ORM\Association\HasMany $Suivis
 * @property \Cake\ORM\Association\BelongsTo $Fournisseurs
 * @property \Cake\ORM\Association\BelongsTo $Users
 * 
 * @method \App\Model\Entity\Materiel get($primaryKey, $options = [])
 * @method \App\Model\Entity\Materiel newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\Materiel[] newEntities(array $data, array $options = [])
 * @method \App\Model\Entity\Materiel|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\Materiel patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\Materiel[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\Materiel findOrCreate($search, callable $callback = null, $options = [])
 * 
 * @mixin \Cake\ORM\Behavior\TimestampBehavior
 * 
 */


// Max 10 ans entre 2 dates
//const MAX_DIFF_YEARS = 10;

class MaterielsTable extends AppTable
{

    public $ALL_STATUS = array(
        'CREATED',
        'VALIDATED',
        'TOBEARCHIVED',
        'ARCHIVED'
    );

    public function toto() { return "titi"; }
    
    /**
     * Initialize method
     *
     * @param array $config
     *            The configuration for the Table.
     * @return void
     * 
     * voir https://book.cakephp.org/4/fr/orm/associations.html
     * 
     */
    public function initialize(array $config)
    {
        parent::initialize($config);
        
        $this->setTable('materiels');
        //$this->setDisplayField('id');
        $this->setDisplayField('designation');
        $this->setPrimaryKey('id');
        //$this->setTable('materiels');
        //$this->setDisplayField('id');
        //$this->setPrimaryKey('id');

        // So that 'created' and 'updated' fields are filled
        $this->addBehavior('Timestamp');
        
        $this->belongsTo('SurCategories', [
            'foreignKey' => 'sur_categorie_id'
        ]);
        $this->belongsTo('Categories', [
            'foreignKey' => 'categorie_id'
        ]);
        $this->belongsTo('SousCategories', [
            'foreignKey' => 'sous_categorie_id'
        ]);
        $this->belongsTo('GroupesThematiques', [
            'foreignKey' => 'groupes_thematique_id'
        ]);
        $this->belongsTo('GroupesMetiers', [
            'foreignKey' => 'groupes_metier_id'
        ]);
        $this->belongsTo('Projets', [
            'foreignKey' => 'projet_id'
        ]);
        
        $this->belongsTo('Organismes', [
            'foreignKey' => 'organisme_id'
        ]);
        $this->belongsTo('Sites', [
            'foreignKey' => 'site_id'
        ]);
        
        $this->hasMany('Documents')
            ->setForeignKey('materiel_id')
            ->setDependent(true); // si le matos est supprimé, les docs attachés le seront aussi
            /*
            ->setConditions($conditions) // un tableau de conditions compatibles avec find() ou des chaînes SQL comme ['Comments.visible' => true].
            ->setSort($sort) // un tableau compatible avec les clauses order de find() ou les chaînes SQL comme ['Comments.created' => 'ASC'].
            ->setCascadeCallbacks($cascadeCallbacks) // Quand ceci et dependent sont à true, les suppressions en cascade chargeront les entities supprimés pour que les callbacks soient correctement lancés. Si à false. deleteAll() est utilisée pour retirer les données associées et aucun callback ne sera lancé.
            ->setFinder($finder) // La méthode finder à utiliser lors du chargement des enregistrements associés.
            */
        /*
        $this->hasMany('Documents', [
            'foreignKey' => 'materiel_id'
        ]);
        */

        $this->hasMany('Emprunts')
            ->setForeignKey('materiel_id')
            ->setDependent(true); // si le matos est supprimé, les emprunts liés le seront aussi
        
        $this->hasMany('Suivis')
            ->setForeignKey('materiel_id')
            ->setDependent(true); // si le matos est supprimé, les suivis liés le seront aussi

        $this->belongsTo('Fournisseurs', [
            'foreignKey' => 'fournisseur_id'
        ]);
        
        // 14/1/19 cake bake auto added:
        /*
        $this->belongsTo('Gestionnaires', [
            'foreignKey' => 'gestionnaire_id'
        ]);
        */
        // 9/6/17 EP added :
        $this->belongsTo('Users', [
            'foreignKey' => 'gestionnaire_id'
        ]);
        /* EP TODO:
        update `materiels` set gestionnaire_id = null where gestionnaire_id = 0;
        ALTER TABLE `materiels`
        ADD CONSTRAINT `fk_materiels_gestionnaire_id` FOREIGN KEY (`gestionnaire_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;
        */        
        $this->belongsTo('Photos', [
            'foreignKey' => 'photo_id'
        ]);
        
        
        
    }
    
    public function dateIsValid($value, array $context) {
        
        /* 
         * $value
         * 
         * La valeur du champ, souvent une string
         * 
         */
        //debug($value);
        
        /* 
         * $context
         * 
         * $context->newRecord est un boolean
         * $context->data est l'entité complète, sous forme d'un tableau qui contient tous les champs (souvent des 'string')
         * 
         */
        //debug($context);
        
        // /^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g
        //$valid = preg_match("/^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/([1-2][0-9]{3})$/",$entity);
        //debug((bool)$valid);
        return (bool) preg_match("/^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/([1-2][0-9]{3})$/",$value);
        
    }
    
    

    /**
     * Default validation rules.
     *
     * (EP 2020)
     * NIVEAU 1 DE VALIDATION : au moment où les données sont converties en Entity avec newEntity(), patchEntity()...
     * On vérifie ici seulement le format des données, indépendamment de leur sens (ce que l'application va en faire)
     * Le niveau 2 "Application Rules" est appliqué par la fonction buildRules() ci-après
     *
     * @param \Cake\Validation\Validator $validator
     *            Validator instance.
     * @return \Cake\Validation\Validator
     */
    // php7
    //public function validationDefault(Validator $validator) : Validator
    public function validationDefault(Validator $validator) //: Validator
    {
        // Check date is dd/mm/yyyy
        /*
        $dateIsValid = function ($entity) {
            //debug($entity); 
            // /^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g
            //$valid = preg_match("/^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/([1-2][0-9]{3})$/",$entity);
            //debug((bool)$valid);
            return (bool) preg_match("/^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/([1-2][0-9]{3})$/",$entity);
        };
        */
        
        /*
        // return true si date n'est pas future (maxi = today)
        $dateIsNotFutureAndNotTooOld = function ($date_string) {
            $tz = new \DateTimeZone('Europe/Paris');
            // DateTime lit les dates au format JJ-MM-YYYY (et non pas JJ/MM/YYYY)
            $date = ( new \DateTime(strtr($date_string,'/','-'),$tz) )->format('Ymd');
            //date_default_timezone_set('Europe/Paris');
            //$today = (new \DateTime('now',$tz))->format('Ymd');
            $today = new \DateTime('now',$tz);
            $date_too_old = $today;
            $today = $today->format('Ymd');
            // today - 50 ans = trop vieux !
            $date_too_old = $date_too_old->sub(new \DateInterval('P50Y'))->format('Ymd');
            /S
            $time = Time::now(); // On récupère la date et l'heure actuelles
            $today = (new date("$time->year-$time->month-$time->day"))->format('Ymd'); // On extrait la date on la formatte en un format comparable de type 20171231
            S/
            /S
            $timeEntity = new time($entity);
            $dateEntity = (new date("$timeEntity->year-$timeEntity->month-$timeEntity->day"))->format('Ymd');
            S/
            /S
            debug($entity); // ex: '20/04/2020'
            debug($today); // '20200717'
            debug($date); // '20200718' => pas bon
            debug($date_too_old);
            S/
            //return ($today >= $date);
            return ($date<=$today && $date>$date_too_old);
        };
        */
        
        /*
        $dateIsNotTooFarAway = function ($date_string) {
            $tz = new \DateTimeZone('Europe/Paris');
            $date = ( new \DateTime(strtr($date_string,'/','-'),$tz) )->format('Ymd');
            $today = new \DateTime('now',$tz);
            $date_too_far = $today;
            $today = $today->format('Ymd');
            // today + 50 ans = trop vieux !
            $date_too_far = $date_too_far->add(new \DateInterval('P50Y'))->format('Ymd');
            return ($date < $date_too_far);
        };
        */
        
        $validator->integer('id')->allowEmpty('id', 'create');
        $validator->notEmpty('designation', 'Ce champ doit être rempli')->add('designation', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->notEmpty('sur_categorie_id', 'Vous devez sélectionner une valeur');
        $validator->notEmpty('categorie_id', 'Vous devez sélectionner une valeur');
        $validator
        //->scalar('numero_laboratoire')
        ->maxLength('numero_laboratoire', 20)
        ->allowEmpty('numero_laboratoire')
        ->add('numero_laboratoire', 'unique', [
            'rule' => 'validateUnique',
            'provider' => 'table'
        ]);
 
        $validator->allowEmpty('description')->add('description', 'valid', [
            'rule' => 'check_string_with_some_special_cars',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        
        $validator->boolean('materiel_administratif')->allowEmpty('materiel_administratif');
        $validator->boolean('materiel_technique')->allowEmpty('materiel_technique');
        
        $validator->add('status', 'valid', [
            'rule' => 'checkStatus',
            'message' => 'Le statut doit prendre une des 4 valeurs CREATED, VALIDATED, TOBEARCHIVED, ou ARCHIVED',
            'provider' => 'table'
        ]);
        /*
        $configuration = TableRegistry::get('Configurations')->find()
            ->where([
            'id =' => 1
        ])
        ->first();
        */
        //$configuration = TableRegistry::getTableLocator()->get('Configurations')->get(1);
        $configuration = TableRegistry::getTableLocator()->get('Configurations')->find()->first();
        
        
        
        // Validation des DATES
        
        // - Date achat
        $f = 'date_acquisition';
        $validator
            ->allowEmptyString($f, $configuration->date_commande_facultative, 'Ce champ doit être rempli')
            ->date($f, 'dmy', 'Date invalide') // https://api.cakephp.org/3.8/class-Cake.Validation.Validation.html#_date
            ->add($f, 'valide0', [
                // 2 façons d'appeler une règle de validation locale :
                // - par son nom de fonction définie LOCALEMENT (ici dans CETTE fonction) comme une variable (avec un $)
                //'rule' => $dateIsValid,
                // - mieux, par son nom, la fonction étant définie n'importe où dans CETTE classe
                'rule' => 'dateIsValid',
                'message' => "La date n'est pas valide (JJ/MM/AAAA)",
                'provider' => 'table',
            ]);
            // migré dans buildRules() car c'est plus de la validation de cohérence
            /*
            ->add($f, 'acceptable', [
                'rule' => $dateIsNotFutureAndNotTooOld,
                'message' => "La date ne doit être ni future ni trop ancienne"
            ]);
            */
        
        // - Date livraison
        $f = 'date_reception';
        $validator
            ->allowEmptyString($f)
            ->date($f, 'dmy', 'Date invalide') // https://api.cakephp.org/3.8/class-Cake.Validation.Validation.html#_date
            //->add('date_reception', 'valid', ['rule' => 'date', 'message' => 'Date invalide']);
            ->add($f, 'valide1', [
                'rule' => 'dateIsValid',
                'message' => "La date n'est pas valide (JJ/MM/AAAA)",
                'provider' => 'table',
            ]);
            /*
            ->add($f, 'acceptable2', [
                'rule' => $dateIsNotTooFarAway,
                'message' => "La date est trop loin dans le futur"
            ]);
            */
            
        // - Date fin garantie
        $f = 'date_fin_garantie';
        $validator
            ->allowEmptyString($f)
            ->date($f, 'dmy', 'Date invalide') // https://api.cakephp.org/3.8/class-Cake.Validation.Validation.html#_date
            ->add($f, 'valide2', [
                'rule' => 'dateIsValid',
                'message' => "La date n'est pas valide (JJ/MM/AAAA)",
                'provider' => 'table',
            ]);
            /*
            ->add($f, 'acceptable3', [
                'rule' => $dateIsNotTooFarAway,
                'message' => "La date est trop loin dans le futur"
            ]);
            */
            
            
        
        /*
        if ($configuration->date_commande_facultative) {
            //$validator->allowEmpty('date_acquisition')->add('date_acquisition', 'custom', [
            $validator
                ->allowEmptyString('date_acquisition')
                ->add('date_acquisition', 'custom1', [
                    'rule' => $dateIsValid,
                    'message' => "La date n'est pas valide (JJ/MM/AAAA)"
                ])
                ->add('date_acquisition', 'custom2', [
                    'rule' => $dateIsNotFuture,
                    'message' => "La date ne doit pas être future"
                ]);
        } else {
            $validator
                //->notEmpty('date_acquisition', 'Ce champ doit être rempli')
                ->allowEmptyString('date_acquisition', false, 'Ce champ doit être rempli')
                ->add('date_acquisition', 'custom1', [
                    'rule' => $dateIsValid,
                    'message' => "La date n'est pas valide (JJ/MM/AAAA)"
                ])
                ->add('date_acquisition', 'custom2', [
                    'rule' => $dateIsNotFutureAndNotTooOld,
                    'message' => "La date ne doit être ni future ni trop ancienne"
                ]);
        }
        */
        /*
        // Attention, configuration désactivée, cela ne génère pas ne num de labo, voir dans config/edit.ctp
        $validator->allowEmpty('fournisseur')->add('fournisseur', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        */
        
        
        
        $validator->numeric('prix_ht')
            ->allowEmpty('prix_ht')
            ->add('prix_ht', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('eotp')->add('eotp', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('numero_commande')->add('numero_commande', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('code_comptable')->add('code_comptable', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('numero_serie')->add('numero_serie', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('numero_inventaire_organisme')->add('numero_inventaire_organisme', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('numero_inventaire_old')->add('numero_inventaire_old', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('date_validated');
        $validator->allowEmpty('date_archived');
        $validator->allowEmpty('photo_id');
        $validator->boolean('etiquette')->allowEmpty('etiquette');
        $validator->boolean('hors_service')->allowEmpty('hors_service');
        $validator->
        // ->notEmpty('site_id', 'Ce champ doit être rempli');
        allowEmpty('site_id');
        $validator->allowEmpty('lieu_detail')->add('lieu_detail', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->notEmpty('nom_responsable')->add('nom_responsable', 'valid', [
            'rule' => 'check_string',
            'message' => 'Ce champ contient des caractères interdits',
            'provider' => 'table'
        ]);
        $validator->allowEmpty('email_responsable')->email('email_responsable');
        $validator->allowEmpty('gestionnaire_id');
        // ->notEmpty('gestionnaire_id', 'Ce champ doit être rempli');
        $validator->allowEmpty('nom_createur');
        $validator
        ->maxLength('nom_modificateur', 45)
        ->allowEmpty('nom_modificateur');
        /* 14/1/19 cake bake autogenerated:
        $validator
        ->scalar('nom_modificateur')
        ->maxLength('nom_modificateur', 45)
        ->allowEmpty('nom_modificateur');
        */
        
        //$validator->allowEmpty('date_reception');
        //$validator->allowEmpty('date_fin_garantie');
        $validator->allowEmpty('duree_garantie');
        $validator->allowEmpty('unite_duree_garantie');
        
        return $validator;

    } // validationDefault()

    public function checkStatus($check) { return ($check !== null && in_array($check, $this->ALL_STATUS)); }

    /*
     public function check_date_d2_gt_d1_but_not_too_much($d2_str,$d1_str) {
     // Si une des 2 dates est nulle => return true
     if (!$d2_str || !$d1_str) return true;
     $tz = new \DateTimeZone('Europe/Paris');
     // DateTime lit les dates au format JJ-MM-YYYY (et non pas JJ/MM/YYYY)
     //$d1 = ( new \DateTime(strtr($entity->date_acquisition,'/','-'),$tz) )->format('Ymd');
     $d1 = new \DateTime(strtr($d1_str,'/','-'),$tz);
     //$d1_text = $d1->format('Ymd');
     $d2 = new \DateTime(strtr($d2_str,'/','-'),$tz);
     //$d2_text = $d2->format('Ymd');
     //date_default_timezone_set('Europe/Paris');
     //$today = (new \DateTime('now',$tz))->format('Ymd');
     //$ok = ($d2_text >= $d1_text);
     if ($d2 < $d1) return false;
     // $d2 > $d1 oui mais pas trop...
     $diff = $d2->diff($d1);
     //debug($diff->y);
     return $diff->y < MAX_DIFF_YEARS;
     }
     */
    
    
    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     * 
     * (EP 2020)
     * NIVEAU 2 DE VALIDATION : au moment où les Entity sont persistées dans la BD
     * On vérifie ici le sens des données (ce que l'application va en faire)
     * Le niveau 1 "Validation Rules" est appliqué par la fonction validationDefault() ci-dessus

     *
     * @param \Cake\ORM\RulesChecker $rules
     *            The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules) //: RulesChecker
    {
        
        //$configuration = TableRegistry::get('Configurations')->find()
        /*
        $configuration = TableRegistry::getTableLocator()->get('Configurations')->find()
            ->where([
            'id =' => 1
        ])->first();
        */
        //$this->config = TableRegistry::getTableLocator()->get('Configurations')->get(1);
        $this->config = TableRegistry::getTableLocator()->get('Configurations')->find()->first();

        // 1) Définition de nos propres règles de gestion
        
        /*
         *  CONTROLE de la cohérence des champs materiel_administratif et materiel_technique
         */
        /*
        $checkAtLeastOneChecked = function ($entity) {
            return ($entity->materiel_administratif || $entity->materiel_technique);
        };
        // return if price >= $configuration->prix_inventaire_administratif € then must be checked as "administratif"
        $checkIfIsAdministratifWhenShouldBe = function ($entity) {
            $configuration = TableRegistry::get('Configurations')->find()
                ->where([
                'id =' => 1
            ])->first();
            return ! ($entity->prix_ht !== null && $entity->prix_ht >= $configuration->prix_inventaire_administratif && ! $entity->materiel_administratif);
        };
        // return if price <800€ then must NOT be checked as "administratif"
        $checkIfIsNotAdministratifWhenShouldNotBe = function ($entity) {
            $configuration = TableRegistry::get('Configurations')->find()
                ->where([
                'id =' => 1
            ])
                ->first();
            return ! ($entity->prix_ht !== null && $entity->prix_ht < $configuration->prix_inventaire_administratif && $entity->materiel_administratif);
        };
        */
        // return if checked as "administratif" price MUST be set
        $checkPriceIfIsAdministratif = function ($entity) {
            //if ($entity->materiel_administratif)
            if (! $entity->materiel_technique) 
                return ($entity->prix_ht >= $this->config->prix_inventaire_administratif); // 1000€ pour l'IRAP
                //return ($entity->prix_ht !== null);
            return true;
        };

        
        // Check DATES
        
        $dateAchatIsNotTooOld = function (Entity $entity) {
            return $entity->check_date_is_not_too_old('date_acquisition');
        };
        $dateAchatIsNotFuture = function (Entity $entity) {
            return $entity->check_date_is_not_future('date_acquisition');
        };
        $dateReceptionIsNotFuture = function (Entity $entity) {
            return $entity->check_date_is_not_future('date_reception');
        };
            
        $dateReceptionIsAfterDateAchatAndNotTooFar = function (Entity $entity) {
            return $entity->check_date_d2_gt_d1_but_not_too_much('date_reception','date_acquisition');
            //return $this->check_date_d2_gt_d1_but_not_too_much($entity->date_reception,$entity->date_acquisition);
            /*
            // Si une des 2 dates est nulle => return true
            if (!$entity->date_reception || !$entity->date_acquisition) return true;
            $tz = new \DateTimeZone('Europe/Paris');
            // DateTime lit les dates au format JJ-MM-YYYY (et non pas JJ/MM/YYYY)
            //$d1 = ( new \DateTime(strtr($entity->date_acquisition,'/','-'),$tz) )->format('Ymd');
            $d1 = new \DateTime(strtr($entity->date_acquisition,'/','-'),$tz);
            //$d1_text = $d1->format('Ymd');
            $d2 = new \DateTime(strtr($entity->date_reception,'/','-'),$tz);
            //$d2_text = $d2->format('Ymd');
            //date_default_timezone_set('Europe/Paris');
            //$today = (new \DateTime('now',$tz))->format('Ymd');
            //$ok = ($d2_text >= $d1_text);
            if ($d2 < $d1) return false;
            // $d2 > $d1 oui mais pas trop...
            $diff = $d2->diff($d1);
            //debug($diff->y);
            return $diff->y < MAX_DIFF_YEARS;
            */ 
        };
        $dateFinGarantieIsAfterDateReceptionAndNotTooFar = function (Entity $entity) {
            //debug($entity);
            return $entity->check_date_d2_gt_d1_but_not_too_much('date_fin_garantie','date_reception');
            //return $this->check_date_d2_gt_d1_but_not_too_much($entity->date_fin_garantie,$entity->date_reception);
            /*
            // Si une des 2 dates est nulle => return true
            if (!$entity->date_reception || !$entity->date_fin_garantie) return true;
            $tz = new \DateTimeZone('Europe/Paris');
            // DateTime lit les dates au format JJ-MM-YYYY (et non pas JJ/MM/YYYY)
            //debug($entity->date_reception);
            //$d1 = ( new \DateTime(strtr($entity->date_reception,'/','-'),$tz) )->format('Ymd');
            $d1 = new \DateTime(strtr($entity->date_reception,'/','-'),$tz);
            $d2 = new \DateTime(strtr($entity->date_fin_garantie,'/','-'),$tz);
            //date_default_timezone_set('Europe/Paris');
            //$today = (new \DateTime('now',$tz))->format('Ymd');
            if ($d2 < $d1) return false;
            // $d2 > $d1 oui mais pas trop...
            $diff = $d2->diff($d1);
            //debug($diff->y);
            return $diff->y < MAX_DIFF_YEARS;
            */
        };
            
        
        // 2) Activation des règles de gestion
        
        /*
        $rules->add($checkAtLeastOneChecked, [
            'errorField' => 'materiel_administratif',
            'message' => 'Le matériel est obligatoirement inventoriable ou technique.'
        ]);
        $rules->add($checkAtLeastOneChecked, [
            'errorField' => 'materiel_technique',
            //'message' => 'Le matériel est obligatoirement inventoriable ou technique.'
            'message' => "Si le matériel n'est pas technique, son prix doit obligatoirement être supérieur à 1000€"
        ]);
        $rules->add($checkIfIsAdministratifWhenShouldBe, [
            'errorField' => 'materiel_administratif',
            'message' => 'Le matériel vaut plus de ' . $configuration->prix_inventaire_administratif . '€ HT, il est donc obligatoirement inventoriable.'
        ]);
        $rules->add($checkIfIsNotAdministratifWhenShouldNotBe, [
            'errorField' => 'materiel_administratif',
            'message' => 'Le matériel vaut moins de ' . $configuration->prix_inventaire_administratif . '€ HT, il n\'est donc pas inventoriable.'
        ]);
        */
        $rules->add($checkPriceIfIsAdministratif, [
            'errorField' => 'prix_ht',
            //'message' => 'Le matériel ne peut pas être inventoriable et ne pas avoir de prix'
            'message' => "Si le matériel n'est pas technique, son prix doit obligatoirement être supérieur à 1000€ (inventoriable)"
        ]);
        $rules->add($checkPriceIfIsAdministratif, [
            'errorField' => 'materiel_technique',
            //'message' => 'Le matériel ne peut pas être inventoriable et ne pas avoir de prix'
            'message' => "Si le matériel n'est pas technique, son prix doit obligatoirement être supérieur à 1000€ (inventoriable)"
        ]);
        
        $rules->add($rules->isUnique([
            'numero_laboratoire'
        ]));
        
        $rules->add($rules->existsIn([
            'sur_categorie_id'
        ], 'SurCategories'));
        $rules->add($rules->existsIn([
            'categorie_id'
        ], 'Categories'));
        $rules->add($rules->existsIn([
            'sous_categorie_id'
        ], 'SousCategories'));
        $rules->add($rules->existsIn([
            'groupes_thematique_id'
        ], 'GroupesThematiques'));
        $rules->add($rules->existsIn([
            'groupes_metier_id'
        ], 'GroupesMetiers'));
        $rules->add($rules->existsIn([
            'projet_id'
        ], 'Projets'));
        $rules->add($rules->existsIn([
            'organisme_id'
        ], 'Organismes'));
        $rules->add($rules->existsIn([
            'site_id'
        ], 'Sites'));
        // 14/1/19 bake autoadded:
        //$rules->add($rules->existsIn(['gestionnaire_id'], 'Gestionnaires'));
        //$rules->add($rules->existsIn(['photo_id'], 'Photos'));
        ///$rules->add($rules->existsIn(['fournisseur_id'], 'Fournisseurs'));
        
        // Check dates achat, reception et fin garantie
        $rules->add($dateAchatIsNotTooOld, [
            'errorField' => 'date_acquisition',
            'message' => "La date ne doit pas être trop ancienne"
        ]);
        $rules->add($dateAchatIsNotFuture, [
            'errorField' => 'date_acquisition',
            'message' => "La date ne doit pas être future"
        ]);
        $rules->add($dateReceptionIsNotFuture, [
            'errorField' => 'date_reception',
            'message' => "La date ne doit pas être future"
        ]);
        $rules->add($dateReceptionIsAfterDateAchatAndNotTooFar, [
            'errorField' => 'date_reception',
            'message' => "La date doit être postérieure à la date d'achat (mais pas trop loin)"
        ]);
        $rules->add($dateFinGarantieIsAfterDateReceptionAndNotTooFar, [
            'errorField' => 'date_fin_garantie',
            'message' => "La date doit être postérieure à la date de livraison (mais pas trop loin)"
        ]);
        
        return $rules;
    } // buildRules()

    /*
    private function getEntity($id) {
        return TableRegistry::getTableLocator()->get('Materiels')->get($id);
    }
    */
    
    private function buy_year_changed($entity) {
        // ADD => on return true car le num inventaire n'a pas encore été généré
        if (is_null($entity->id)) return true;
        // EDIT :
        //debug($this->getEntity($entity->id));exit;
        //$old_date_year = $this->getEntity($entity->id)->date_acquisition->format('Y');
        //$old_date_year = TableRegistry::getTableLocator()->get('Materiels')->get($entity->id);
        $old_date_year = $this->get($entity->id)->date_acquisition->format('Y');
        $new_date_year = $entity->date_acquisition->format('Y');
        //debug($old_date_year); debug($new_date_year); exit;
        return $new_date_year != $old_date_year;
        // Sinon
        return false;
    }
    public function beforeSave($event, $entity, $options)
    {
        //debug($entity); exit;
        if ( ! $entity->get('administrer') ) {
            /* (EP) TODO: ajouter champ nom_ancien_responsable 
            Ca ressemble fort à un bug ce truc !!! (mais je suis pas encore sur de mon bugfix)
            
            if ( !empty($entity->get('nom_responsable')) && empty($entity->get('nom_responsable')) ) {
                $entity->set('nom_responsable', $entity->get('nom_ancien_responsable'));
            
            Une fois le champ nom_ancien_responsable ajouté, on pourra activer ce code:
            // Garder trace du nom du responsable précédent si jamais on change le nom du reponsable
            // Utile aussi si un responsable n'existe plus dans le ldap, on aura toujours son nom dans la BD
            if ( !empty($entity->get('nom_responsable')) && empty($entity->get('nom_ancien_responsable')) ) {
                    $entity->set('nom_ancien_responsable', $entity->get('nom_responsable'));
            }
            */
            /*
            $configuration = TableRegistry::get('Configurations')->find()
                ->where([
                'id =' => 1
            ]) -> first();
            */
            //$configuration = TableRegistry::get('Configurations')->get(1);
            //$configuration = TableRegistry::get('Configurations')->first();
            $configuration = TableRegistry::getTableLocator()->get('Configurations')->find()->first();
            
            
            // numero_laboratoire generator (QC changed this in Jan 2015)
            // (EP) Set new $labNumber (laboratory number) for this new materiel
            /* (EP202009) 
             * C'est plutot bizarre ce code... pas logique :
            $WITH_YEAR = FALSE; 
            $DATE_GIVEN = TRUE;
            if (! $configuration->numero_labo_sans_annee) {
                $WITH_YEAR = TRUE; 
                $DATE_GIVEN = !empty($entity->get('date_acquisition'));
            }
             * ... Je remplace donc par plus simple et lisible :
             */
            $WITH_YEAR = ! $configuration->numero_labo_sans_annee;
            $DATE_GIVEN = !empty($entity->get('date_acquisition'));
            
            // Si pas de date achat, on ne sauve pas l'entité
            if (!$DATE_GIVEN) return false;
            
            // (EP 202007)
            // AVANT : Numero inventaire généré 1 seule fois (la toute première)
            //if ( empty($entity->get('numero_laboratoire')) && $DATE_GIVEN ) {
            // NOW : Numero inventaire regénéré A CHAQUE FOIS que la date achat CHANGE, c'est mieux !!!
            /*
            $DATE_CHANGED = false;
            // ADD : par défaut on considère que la date a changé
            if ( empty($entity->get('numero_laboratoire')) ) 
                $DATE_CHANGED = true;
            // EDIT : on regarde si la date a changé
            else {
                $e = TableRegistry::getTableLocator()->get('Materiels')->get($entity->id);
                if ($entity->date_acquisition != $e->date_acquisition) $DATE_CHANGED = true;
            }
            $DATE_CHANGED = $DATE_GIVEN && $DATE_CHANGED;
            if ( $DATE_CHANGED ) {
            */
            //if ( $DATE_GIVEN ) {
            //debug($DATE_CHANGED);
            //debug($entity->isDirty('date_acquisition'));
            //debug(in_array($entity->date_acquisition,$entity->getDirty())); 
            //debug($entity); 
            //if ( $entity->isDirty('date_acquisition') ) {
            /* 
             * (EP202009) changement radical de l'algo de (re)-génération du numéro d'inventaire
             * 
             * Si config "SANS année" (simple numéro séquentiel) 
             * => on ne génère le numéro d'inventaire
             * QU'une seule fois pour toutes : à la création de la fiche
             * 
             * Par contre, si config "AVEC année" (numéro séquentiel PAR année)
             * => on re-génère le numéro d'inventaire à CHAQUE fois que 
             * - la date achat est modifiée
             * ET
             * - que son ANNÉE a changé
             */
            if (
                // ADD => on génère car c'est la première fois
                empty( $entity->get('numero_laboratoire') )
                ||
                // EDIT et config "avec" année => on (re)-génère ssi date achat modifiée et année changée
                ( $WITH_YEAR && $entity->isDirty('date_acquisition') && $this->buy_year_changed($entity) )
            ) {
                $labShortName = $configuration->labNameShort;
                $numero_laboratoire = $labShortName;
                if ($WITH_YEAR) {
                    $year=substr($entity->get('date_acquisition'), 6, 4);
                    ////debug("year before : $year");
                    if (strlen($year) == 2) {
                        $year = '20' . $year;
                    }
                    ////debug("year after: $year");
                    $numero_laboratoire .= '-' . $year;
                }
                //$num = TableRegistry::get('Materiels')->find('all', [
                $num = TableRegistry::getTableLocator()->get('Materiels')->find('all', [
                    'fields' => [
                        'numero_laboratoire'
                    ],
                    'conditions' => [
                        'Materiels.numero_laboratoire LIKE' => $numero_laboratoire . '%'
                    ],
                    'order' => [
                        'Materiels.numero_laboratoire DESC'
                    ]
                ])->first()['numero_laboratoire'];
                //error_log($num);
                //var_dump($num);
                //debug($num); exit;
                #$newId = substr($num, -4) + 1;
                # To avoid a "warning: a non-numeric value encountered" eror:
                $newId = (int)substr($num, -4) + 1;
                //error_log($newId);
                //debug($newId);
                $labNumber = $numero_laboratoire . '-' . sprintf("%04d", $newId);
                $entity->set('numero_laboratoire', $labNumber);
                //$entity->setError('jkl', 'toto');
            } // date achat changée
        }
        if (empty($entity->get('date_acquisition'))) {
            $entity->set('date_acquisition', null);
        }
        if (empty($entity->get('date_reception'))) {
            $entity->set('date_reception', null);
        }
        
        /* (EP 20200402) TRES GROS Traitement du fournisseur (SSI sa valeur a changé)
         * 
         * On récupère le NOM du fournisseur saisi dans le champ appelé fournisseur.name (en php)
         * En Html ça correspond à fournisseur['name']
         * Si saisie vide => mettre à null
         * Sinon, chercher le fournisseur_id correspondant à ce nom
         * Si id trouvé => fournisseur_id = cet id
         * Sinon, créer un nouveau fournisseur avec ce nom
        */
        //debug($entity->fournisseur['name']);exit;
        //debug($entity); exit;
        $fournisseur_asis = $entity->fournisseur['name'];
        // Enlever les espaces superflus
        $fournisseur = trim($fournisseur_asis);
        //debug($fournisseur);
        //debug($entity->fournisseur_orig);
        //exit;
        // L'utilisateur a changé le fournisseur ou alors lui a enlevé des espaces ?
        // => on traite ce changement
        if ($fournisseur_asis != $entity->fournisseur_orig) {
            // Champ fournisseur vide => mettre à nul
            if ($fournisseur == '')
                $entity->fournisseur_id = null;
            // Pas vide => on récupère l'id de ce fournisseur (s'il existe)...
            else {
                $fournisseurs = TableRegistry::getTableLocator()->get('Fournisseurs');
                // Attention, le find est insensible à la CASSE
                $fournisseurs_existants = $fournisseurs->find()
                    ->where([
                        // marche pas, obligé d'utiliser orWhere !!!!!!
                        //'OR' => ['nom' => $fournisseur_asis, 'nom' => $fournisseur]
                        'nom in' => [$fournisseur_asis, $fournisseur]
                        //'nom' => $fournisseur_asis,
                    ]);
                    /*
                    ->orWhere([
                        //'OR' => ['nom' => $fournisseur_asis, 'nom' => $fournisseur]
                        'nom' => $fournisseur
                    ])
                    */
                //debug($fournisseurs_existants);
                //foreach ($fournisseurs_existants as $f) debug($f); exit;
                /* Normalement, il ne devrait y avoir qu'un seul fournisseur dans la liste
                 * Mais il peut y en avoir plusieurs variantes, avec des espaces inutiles
                 * Il faudrait les supprimer, mais on ne peut pas car ils sont liés à des matériels.
                 * Donc, on itère jusqu'à trouver celui qui a un nom sans espaces inutiles.
                 * Au pire, on prendra le dernier de la liste
                 */
                //->first();
                // Par défaut, pas trouvé
                $fournisseur_existant = null;
                foreach ($fournisseurs_existants as $fournisseur_existant)
                    if ($fournisseur_existant->nom == $fournisseur) break;
                //debug($fournisseur_existant);
                // Fournisseur changé (autre nom) ? (vrai changement de fournisseur, c'est à dire "autre fournisseur")
                //if ( $fournisseur != trim($entity->fournisseur_orig) )  {
                if ( strtoupper($fournisseur) != strtoupper(trim($entity->fournisseur_orig)) )  {
                    //debug("changé");exit;
                    /* ********* UPDATE fournisseur ID *********
                     * C'est un fournisseur déjà existant ?
                     * => positionner fournisseur_id dessus
                     */
                    if ($fournisseur_existant) {
                        //debug('changement'); exit;
                        $entity->fournisseur_id = $fournisseur_existant->id;
                    }
                    /* ********* ADD new fournisseur dans la BD *********
                     * C'est vraiment un NEW fournisseur 
                     * => le créer
                     * => positionner fournisseur_id dessus
                     */
                    else {
                        $fournisseur_new = $fournisseurs->newEntity(['nom' => $fournisseur]);
                        $fournisseurs->save($fournisseur_new);
                        $entity->fournisseur_id = $fournisseur_new->id;
                        //}
                    } // NEW
                } // fournisseur changé
                //debug($fournisseur_new);
                //debug($entity->fournisseur_id);
                //exit;
                
                
                /* Le fournisseur existe déjà, on regarde :
                 * - si on doit lui supprimer des espaces en trop (dans la BD)
                 * OU
                 * - si on doit repositionner l'id actuel sur ce fournisseur (qui n'a pas d'espace en trop)
                 */
                if ($fournisseur_existant) {
                    /* ********* UPDATE fournisseur présent dans la BD *********
                     * Si le fournisseur saisi est le même que celui trouvé en BD MAIS écrit différemment
                     * - soit pcq'il a des espaces en trop (dans la BD),
                     * - soit pcq la CASSE est différente
                     * => on profite de le corriger (dans la BD) en prenant pour ref celui saisi
                     * (tant qu'à faire, pourquoi se priver de ce petit plus qui permet de mettre à jour un fournisseur)
                     */
                    if ($fournisseur != $fournisseur_existant->nom) {
                        //debug('update fournisseur existant');
                        // update
                        $fournisseur_existant->nom = $fournisseur;
                        $fournisseurs->save($fournisseur_existant);
                    }
                    /* ********* UPDATE fournisseur ID *********
                     * On a trouvé dans la BD un fournisseur qui n'a pas d'espace et c'est aussi ce qui a été saisi
                     * => on positionne l'id sur ce fournisseur là (plutôt que l'actuel qui contient des espaces)
                     */
                    else if ($fournisseur_asis == $fournisseur_existant->nom) {
                        //debug('update fournisseur_id');
                        // update
                        $entity->fournisseur_id = $fournisseur_existant->id;
                        $fournisseurs->save($fournisseur_existant);
                    }
                    //exit;
                }
            } // fournisseur non vide
        } // ssi changement
        // Finalement, on supprime les champs 'fournisseur' car sinon erreur de sauvegarde, 
        // (normal ces champs n'existent pas dans materiel) 
        unset($entity->fournisseur_orig);
        unset($entity->fournisseur);
        //debug($entity); exit;
        
        // TRES IMPORTANT, sinon echec de la sauvegarde !!!
        return true;
    }
}