'Désignation', 'description' => 'Description', //'permanent', //'will_stay', // O/N 'sur_categorie_id' => 'Domaine', 'categorie_id' => 'Catégorie', // - Utilisateur 'nom_user' => "Nom de l'utilisateur de ce matériel", // - Acheteur 'nom_responsable' => 'Nom du responsable', // (rempli automatiquement) 'email_responsable' => 'Email du responsable', // Calculé auto au moment du save() //'numero_laboratoire', 'organisme_id' => 'Organisme', 'prix_ht' => 'Prix HT', // Optionnel car par défaut = acheteur //'resp_credit' => 'Responsable du crédit', //TODO: a remettre ? avec "je ne sais pas" /////'gestionnaire_id' => 'Gestionnaire de référence', //'fournisseur', //'devis joint', // Utilisé par la Gestion pour remplir le champ eotp 'budgets' => 'Budgets', // INFOS ADMINISTRATIVES // - EOTP : obligatoire seulement dans LOT2 //'eotp' => 'Entité(s) dépensière(s) (budget(s))', // ligne budgétaire (sur quel(s) budget(s)) ou entité(s) dépensière(s) ]; // $MANDATORY_FIELDS_FOR_LOT1 const MANDATORY_FIELDS_FOR_LOT2 = [ //'fournisseur_id' => 'Fournisseur', //'fournisseur' => 'Fournisseur', 'date_acquisition' => "Date d'achat", 'date_reception' => 'Date de livraison', //'etiquette', // O/N 'site_id' => 'Site', 'lieu_detail' => 'Lieu de stockage', // INFOS ADMINISTRATIVES : // La Gestion doit remplir ce champ a partir des infos qui sont dans le champ "budget" (rempli par acheteur) 'eotp' => 'Entité(s) dépensière(s) (budget(s))', // ligne budgétaire (sur quel(s) budget(s)) ou entité(s) dépensière(s) 'numero_commande' => 'Num. BC', 'numero_inventaire_organisme' => "N° inventaire de l'organisme", ]; //const MANDATORY_FIELDS_FOR_LOT1_IP2I = [ define ('MANDATORY_FIELDS_FOR_LOT1_IP2I', [ // Infos toujours obligatoires (cachées car calculées automatiquement) //'status', //'tobeordered', //'hors_service', // O/N 'designation' => 'Désignation', 'description' => 'Description', //'permanent', //'will_stay', // O/N 'sur_categorie_id' => 'Domaine', 'categorie_id' => 'Catégorie', 'fournisseur_id' => 'Fournisseur', // - Utilisateur 'nom_user' => "Nom de l'utilisateur de ce matériel", // - Acheteur 'nom_responsable' => 'Nom du responsable', // (rempli automatiquement) 'email_responsable' => 'Email du responsable', // Calculé auto au moment du save() //'numero_laboratoire', ////'organisme_id' => 'Organisme', ////'prix_ht' => 'Prix HT', // Optionnel car par défaut = acheteur //'resp_credit' => 'Responsable du crédit', //TODO: a remettre ? avec "je ne sais pas" /////'gestionnaire_id' => 'Gestionnaire de référence', //'fournisseur', //'devis joint', // Utilisé par la Gestion pour remplir le champ eotp ////'budgets' => 'Budgets', 'date_acquisition' => "Date d'achat", ////'date_reception' => 'Date de livraison', //'etiquette', // O/N ///'site_id' => 'Site', ///'lieu_detail' => 'Lieu de stockage', // INFOS ADMINISTRATIVES : // La Gestion doit remplir ce champ a partir des infos qui sont dans le champ "budget" (rempli par acheteur) ///'eotp' => 'Entité(s) dépensière(s) (budget(s))', // ligne budgétaire (sur quel(s) budget(s)) ou entité(s) dépensière(s) ////'numero_commande' => 'Num. BC', ////'numero_inventaire_organisme' => "N° inventaire de l'organisme", ]); // $MANDATORY_FIELDS_FOR_LOT1 //const MANDATORY_FIELDS_FOR_LOT2_IP2I = MANDATORY_FIELDS_FOR_LOT1_IP2I; define ('MANDATORY_FIELDS_FOR_LOT2_IP2I', MANDATORY_FIELDS_FOR_LOT1_IP2I); */ class MaterielsTable extends AppTable { public static $LAB = null; private $PREV_SEUIL_INVENTORIABLE = 800; private $LAST_SEUIL_INVENTORIABLE_YEAR = 2020; private $LAST_SEUIL_INVENTORIABLE_DATE = '03/06/2020'; private $LAST_SEUIL_INVENTORIABLE; // 1000€ pour IRAP (depuis 3/6/2020) public $ALL_STATUS = array( 'CREATED', 'TOBEORDERED', 'VALIDATED', 'TOBEARCHIVED', 'ARCHIVED' ); public function toto() { return "titi"; } public static function getLabName() { // Singleton (on ne lit qu'1 seule fois la config !!!) if (! self::$LAB) { $confLabinvent = TableRegistry::getTableLocator()->get('Configurations')->find()->first(); if (is_null($confLabinvent)) throw new \Exception("EXCEPTION (from Model): La table 'configurations' de la base de données est vide"); self::$LAB = $confLabinvent->labNameShort ? $confLabinvent->labNameShort : 'LABO'; //debug(self::$LAB); } return self::$LAB; } private static function getConfigKey($k) { if ( Configure::check($k) ) { $k = Configure::readOrFail($k); } else debug("key $k is not in yaml config file"); return $k; } /* (EP 2021 09) LOT N total = LOT N-1 + LOT N * - LOT0 total = [] + LOT0 * - LOT1 total = LOT0 + LOT1 * - LOT2 total = LOT1 + LOT2 * * Algo RECURSIF */ public static function getCategoryFieldsForLot($categ, $lot_num, $categ_initial=null) { //debug($lot_num); // ex: $categ = RECOMMENDED_FIELDS_AFTER_LOT //if ($lot_num < 0) return $categ_initial ? Configure::readOrFail('UNEDITABLE_FIELDS') : []; if ($lot_num < 0) return $categ_initial ? Configure::readOrFail("MANDATORY_AND_READONLY_FIELDS.$categ_initial") : []; // Recursive call $base_fields = self::getCategoryFieldsForLot($categ, $lot_num-1, $categ_initial); $base_fields = self::removeUnselectedFieldsFrom($base_fields); //debug($base_fields); //$new_fields = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS.RECOMMENDED_FIELDS_AFTER_LOT'.$lot_num); $new_fields = Configure::readOrFail("MANDATORY_AND_READONLY_FIELDS.$categ".$lot_num); $new_fields = self::removeUnselectedFieldsFrom($new_fields); //debug($new_fields); //debug($new_fields); return array_merge($base_fields, $new_fields); } /* * Retourne tous les matos mais SANS les matos sensibles de sites différents du user * * Usage : $query = $matos->find('filteredForUser', ['user' => $userEntity]); * * */ public function findFilteredForUserSite(Query $query, array $options) { /* $user = $options['user']; $matos_id_sensibles_from_other_sites = $this->find()->where(['is_site_only'=>1])->andWhere(['site_id !='=>$user->site_id])->select(['id']); */ $user_site_id = $options['user_site_id']; // Liste des matos (id) interdits d'accès au user dont le site est $user_site_id // => c.a.d liste des matos "sensibles" dont le site est différent de celui du user ($user_site_id) //$matos_id_sensibles_from_other_sites = $this->find()->where(['is_site_only'=>1])->andWhere(['site_id !='=>$user_site_id])->select(['id']); // all()->extract('title'); //$matos_id_sensibles_from_other_sites = $this->find()->where(['is_site_only'=>1]); //$matos_id_sensibles_from_other_sites = $this->find()->select(['id','is_site_only','site_id'])->where(['is_site_only'=>1]); $matos_id_sensibles_from_other_sites = $this->find()->select(['id'])->where(['is_site_only'=>true]); //$matos_id_sensibles_from_other_sites = $this->find()->select(['id'])->where(['is_site_only'=>1]); //debug($user_site_id); if (! is_null($user_site_id)) $matos_id_sensibles_from_other_sites->where(['site_id !=' => $user_site_id]); //debug("1"); debug($matos_id_sensibles_from_other_sites); //foreach ($matos_id_sensibles_from_other_sites as $m) debug($m->id); //$matos_id_sensibles_from_other_sites->select(['id']); // all()->extract('title'); //$matos_id_sensibles_from_other_sites->all()->extract('id'); // all()->extract('title'); //foreach ($matos_id_sensibles_from_other_sites as $m) debug($m); exit; //return $query->where(['id !=' => $matos_id_sensibles_from_other_sites]); // Liste de tous les matos SAUF les interdits //$query->where(['materiels.id not in' => $matos_id_sensibles_from_other_sites]); $query->where(['Materiels.id not in' => $matos_id_sensibles_from_other_sites]); //debug("3"); debug($query); return $query; // Ceci est équivalent au find('list') //$keyValueList = $articles->find()->combine('id', 'title'); } public static function removeUnselectedFieldsFrom($fields) { /* * Exemple : Before remove : { designation: { selected: '0', comment: commentaire, roles: '' }, description: { selected: '0', comment: autre, roles: '' }, nom_responsable: { selected: '1', comment: 'nom du responsable', } After remove : { nom_responsable: { selected: '1', comment: 'nom du responsable', } */ //$fields_selected = []; foreach ($fields as $field_name => $attributes) { if ( $field_name=='fieldset_comment' || !$attributes['selected'] ) unset($fields[$field_name]); } return $fields; } public static function getMandatoryFieldsForLot($lot_num) { return self::getCategoryFieldsForLot('MANDATORY_FIELDS_FOR_LOT', $lot_num); /* $specific_constant_name = "MANDATORY_FIELDS_FOR_LOT1_".self::getLabName(); $mandatory_fields_lot1 = defined($specific_constant_name) ? constant($specific_constant_name) : MANDATORY_FIELDS_FOR_LOT1; return $mandatory_fields_lot1; */ /* if ($lot_num < 0) return []; $base_fields = self::getMandatoryFieldsForLot($lot_num-1); $new_fields = Configure::readOrFail('MANDATORY_FIELDS_FOR_LOT'.$lot_num); //debug($new_fields); return array_merge($base_fields, $new_fields); */ } // RECURSIF public static function getRecommendedFieldsForLot($lot_num) { return self::getCategoryFieldsForLot('RECOMMENDED_FIELDS_AFTER_LOT', $lot_num); /* if ($lot_num < 0) return []; $base_fields = self::getRecommendedFieldsForLot($lot_num-1); $new_fields = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS.RECOMMENDED_FIELDS_AFTER_LOT'.$lot_num); //debug($new_fields); return array_merge($base_fields, $new_fields); */ } // (EP 2021 09) fonction récursive : // => champs readonly pour le lot N = champs readonly pour le lot N-1 + champs readonly pour le lot N : public static function getUneditableFieldsForLot($lot_num) { return self::getCategoryFieldsForLot('UNEDITABLE_FIELDS_AFTER_LOT', $lot_num, 'UNEDITABLE_FIELDS'); /* // Si N = -1 => on retourne la liste initiale de champs readonly (fin de la récursivité) //if ($lot_num < 0) return []; if ($lot_num < 0) return self::list_to_dict( Configure::readOrFail('UNEDITABLE_FIELDS') ); // - Champs readonly pour le lot N-1 (appel récursif) $base_uneditable_fields = self::getUneditableFieldsForLot($lot_num-1); //debug($base_uneditable_fields); // - Champs readonly pour le lot N $new_uneditable_fields = self::list_to_dict( Configure::readOrFail('UNEDITABLE_FIELDS_AFTER_LOT'.$lot_num) ); //$new_uneditable_fields = self::list_to_dict($new_uneditable_fields); //debug($new_uneditable_fields); // => Somme des 2 (champs readonly pour le lot N-1 + champs readonly pour le lot N) //return array_unique(array_merge($base_uneditable_fields, $new_uneditable_fields)); return array_merge($base_uneditable_fields, $new_uneditable_fields); */ } public static function separateFieldNameAndExceptedRoles($f) { $except_roles = []; $except_str = '(sauf'; $pos0 = strpos($f, '('); $pos = strpos($f, $except_str); // Si le nom du champ $f fini par "(sauf ...)", on récupère cette chaine except (sans le mot 'sauf' ni la parenthèse finale) dans $except if ($pos0 !== false) { $error_msg = "Erreur dans le fichier de configuration des champs non modifiables"; // EXCEPTION si parenthèse début mais pas suivie du mot-clé 'sauf' /* if ($pos === false) throw new \Exception("$error_msg : une parenthèse doit toujours être suivie du mot-clé 'sauf', ce qui n'est pas le cas de la ligne '$f'"); */ if ($pos !== false) { // EXCEPTION si pas de parenthèse finale, ou role mal orthographié //$except_list = trim( substr($f, $pos + strlen($except_str), -1) ); if ( substr($f, -1) != ')' ) throw new \Exception("$error_msg : il manque une parenthèse à la fin de la ligne '$f'"); $except_roles = str_replace(" ", "", substr($f, $pos + strlen($except_str), -1) ); $except_roles = explode(',', $except_roles); foreach ($except_roles as $role) if (! self::isValidRole($role)) throw new \Exception("$error_msg : le role '$role' est mal orthographié dans la ligne '$f'"); $f = trim( substr($f,0,$pos) ); } } //return [$f=>$except_roles]; return ['name'=>$f, 'roles'=>$except_roles]; } // (EP 2021 10) public static function list_to_dict($uneditable_fields) { $dict = []; foreach ($uneditable_fields as $f) { //$dict[$f] = $except_roles; $fieldNameAndExceptedRoles = self::separateFieldNameAndExceptedRoles($f); $dict[$fieldNameAndExceptedRoles['name']] = $fieldNameAndExceptedRoles['except_roles']; } return $dict; } /* public static function getMandatoryFieldsLot1() { /S $specific_constant_name = "MANDATORY_FIELDS_FOR_LOT1_".self::getLabName(); $mandatory_fields_lot1 = defined($specific_constant_name) ? constant($specific_constant_name) : MANDATORY_FIELDS_FOR_LOT1; return $mandatory_fields_lot1; S/ return Configure::readOrFail('MANDATORY_FIELDS_FOR_LOT1'); //return Configure::readOrFail('MANDATORY_FIELDS_FOR_LOT1'); } public static function getMandatoryFieldsLot2() { /S $constantName = "MANDATORY_FIELDS_FOR_LOT2_".self::getLabName(); $mandatory_fields_lot2 = defined($constantName) ? constant($constantName) : MANDATORY_FIELDS_FOR_LOT2; return array_merge(self::getMandatoryFieldsLot1(), $mandatory_fields_lot2); S/ $mandatory_fields_lot2 = Configure::readOrFail('MANDATORY_FIELDS_FOR_LOT2'); return array_merge(self::getMandatoryFieldsLot1(), $mandatory_fields_lot2); } */ public static function getMandatoryFieldsForMateriel($mat) { $status = $mat->status; $fields = self::getMandatoryFieldsForMaterielStatus($status); // Seulement si prix > 10K€ : exiger la facture jointe et le n° série et lieu de stockage précis if ($status == 'VALIDATED' && $mat->prix_ht && $mat->prix_ht > 10000) { //$fields['numero_serie'] = 'S/N'; /* $fields['numero_serie'] = [ //'selected'=>'1', 'comment'=>'S/N' ]; */ //TODO 202109 //$fields[] = 'lieu stockage'; //$fields[] = 'facture jointe'; } return $fields; } public static function getMandatoryFieldsForMaterielStatus($status) { //return self::getCategoryFieldsForMaterielStatus('MANDATORY_FIELDS_FOR_LOT', $status); return self::getCategoryFieldsForMaterielStatus('MANDATORY', $status); /* if (is_null($status)) $status='CREATED'; // On recup le LOT qui convient au status courant du matos //$fields = ($status == 'CREATED') ? self::getMandatoryFieldsForLot(1) : self::getMandatoryFieldsForLot(2); // CREATED => LOT0 if ($status == 'CREATED') $lot_num = 0; // TOBEORDERED => LOT1 elseif ($status == 'TOBEORDERED') $lot_num = 1; // >= VALIDATED => LOT2 else $lot_num=2; $fields = self::getMandatoryFieldsForLot($lot_num); //debug($fields); return $fields; */ } public static function getUneditableFieldsForMaterielStatus($status) { //return self::getCategoryFieldsForMaterielStatus('UNEDITABLE_FIELDS_AFTER_LOT', $status); return self::getCategoryFieldsForMaterielStatus('UNEDITABLE', $status); /* if (is_null($status)) $status='CREATED'; // On recup le LOT qui convient au status courant du matos //$fields = ($status == 'CREATED') ? self::getMandatoryFieldsForLot(1) : self::getMandatoryFieldsForLot(2); // CREATED => LOT0 if ($status == 'CREATED') $lot_num = 0; // TOBEORDERED => LOT1 elseif ($status == 'TOBEORDERED') $lot_num = 1; // >= VALIDATED => LOT2 else $lot_num=2; $fields = self::getUneditableFieldsForLot($lot_num); return $fields; */ } public static function getRecommendedFieldsForMaterielStatus($status) { //return self::getCategoryFieldsForMaterielStatus('RECOMMENDED', $status); //return self::getCategoryFieldsForMaterielStatus('RECOMMENDED_FIELDS_AFTER_LOT', $status); return self::getCategoryFieldsForMaterielStatus('RECOMMENDED', $status); } public static function getCategoryFieldsForMaterielStatus($categ, $status) { //$func_name = "get".ucfirst($fields_categ)."FieldsForLot"; // getUneditableFieldsForLot() //$func_name = "getCategoryFieldsForLot"; // getUneditableFieldsForLot() $func_name = "get".ucfirst($categ)."FieldsForLot"; // getUneditableFieldsForLot() if (is_null($status)) $status='CREATED'; // On recup le LOT qui convient au status courant du matos //$fields = ($status == 'CREATED') ? self::getMandatoryFieldsForLot(1) : self::getMandatoryFieldsForLot(2); // CREATED => LOT0 if ($status == 'CREATED') $lot_num = 0; // TOBEORDERED => LOT1 elseif ($status == 'TOBEORDERED') $lot_num = 1; // >= VALIDATED => LOT2 else $lot_num=2; //$fields = self::getUneditableFieldsForLot($lot_num); $fields = self::$func_name($lot_num); //debug($fields); return $fields; } /** * 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); //debug($config); // (IP2I) // Champs obligatoires // - PHP /* $mf1 = Configure::readOrFail('php_MANDATORY_FIELDS_FOR_LOT1'); debug($mf1); $mf1 = Configure::readOrFail('php_MANDATORY_FIELDS_FOR_LOT1_IP2I'); debug($mf1); */ /* // - YML $keys = ['MANDATORY_FIELDS_FOR_LOT0', 'MANDATORY_FIELDS_FOR_LOT1', 'MANDATORY_FIELDS_FOR_LOT2']; foreach ($keys as $k) { //debug($k . ' :'); if ( Configure::check($k) ) { $k = Configure::readOrFail($k); //debug($k); } else debug("error on key $k"); } */ /* Autorisations $keys = ['GeneralAuthorizations', 'MaterielsAuthorizations', 'SuivisAuthorizations', 'EmpruntsAuthorizations']; foreach ($keys as $k) if ( Configure::check($k) ) { debug($k . ' :'); $k = Configure::readOrFail($k); debug($k); } else debug("error on key $k"); */ /* $confLabinvent = TableRegistry::getTableLocator()->get('Configurations')->find()->first(); if (is_null($confLabinvent)) throw new \Exception("EXCEPTION (from Model): La table 'configurations' de la base de données est vide"); self::$LAB = $confLabinvent->labNameShort ? $confLabinvent->labNameShort : 'LABO'; //debug(self::$LAB); */ $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'); /* * BELONGS TO */ $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->belongsTo('Fournisseurs', [ 'foreignKey' => 'fournisseur_id' ]); // A virer ? $this->belongsTo('Photos', [ 'foreignKey' => 'photo_id' ]); // belongs to Gestionnaire // 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; */ /* * HAS MANY */ $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 } 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); } public static function getNiceStatus($status) { //if ($this->is_created) return 'À VALIDER'; if ($status == 'CREATED') return 'CRÉÉ - à valider'; if ($status == 'TOBEORDERED') return 'EN COMMANDE - à valider'; if ($status == 'VALIDATED') return 'VALIDÉ - livré & payé'; if ($status == 'TOBEARCHIVED') return 'À SORTIR'; return 'ARCHIVÉ'; } /** * 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 { /* if (IP2I) { //$this->MANDATORY_FIELDS_FOR_LOT1['fournisseur_id'] = 'Fournisseur'; //$this->MANDATORY_FIELDS_FOR_LOT1['fournisseur.name'] = 'Fournisseur'; $optional_fields = [ 'organisme_id', 'prix_ht', 'budgets', 'eotp', 'numero_commande', 'date_reception', 'gestionnaire_id', ]; foreach ($optional_fields as $fname) { // LOT1 unset($this->MANDATORY_FIELDS_FOR_LOT1[$fname]); // LOT2 unset($this->MANDATORY_FIELDS_FOR_LOT2[$fname]); $validator->allowEmptyString($fname, true); } } //print_r($this->MANDATORY_FIELDS_FOR_LOT1); */ /* // TODO : améliorer ça. C'est lot1 si CREATED, et lot2 si VALIDATED+ $lot = $this->getMandatoryFieldsLot1(); // (EP 31/5/21) Champs obligatoires (LOT1) foreach ($lot as $fname=>$fdisp) { // Champs spéciaux dont le caractère obligatoire est géré indirectement (via beforeSave()) // (fournisseur_id, et autres champs ajoutés dans l'avenir ? ...) if ($fname=='fournisseur_id') continue; $validator->allowEmptyString($fname, false, 'Ce champ doit être rempli'); } */ // 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'); $f = 'designation'; $validator //->notEmpty($f, 'Ce champ doit être rempli') ->add($f, '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'); $f = 'numero_laboratoire'; $validator ->allowEmpty($f) //->scalar('numero_laboratoire') ->maxLength($f, 20) ->add($f, 'unique', [ 'rule' => 'validateUnique', 'provider' => 'table' ]); /* //$validator->allowEmpty('description')->add('description', 'valid', [ $validator->notEmpty('description')->add('description', 'valid', [ 'rule' => 'check_string_with_some_special_cars', 'message' => 'Ce champ contient des caractères interdits', 'provider' => 'table' ]); */ $validator->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, TOBEORDERED, 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 // (EP 20201125 date commande n'est désormais plus obligatoire, sauf pour la VALIDATION) //->allowEmptyDate($f, false, 'Ce champ doit être rempli') //->allowEmptyString($f, $configuration->date_commande_facultative, 'Ce champ doit être rempli') ->allowEmptyDate($f, true) ->allowEmptyString($f, true) ->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 ->allowEmptyDate($f, true) ->allowEmptyString($f, true) ->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'); /* $validator->add($checkPriceIsStrictPositive, [ 'errorField' => 'prix_ht', //'message' => 'Le matériel ne peut pas être inventoriable et ne pas avoir de prix' 'message' => "Le montant doit être supérieur à 0 €" ]); */ $validator->allowEmpty('prix_ht')->add('prix_ht', 'valid', [ 'rule' => 'checkPriceIsPositive', 'message' => 'Le prix doit être numérique et positif', 'provider' => 'table' ]); // (EP202010 prix obligatoire) //->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->notEmpty('organisme_id','Ce champ doit être précisé'); //$validator->notEmpty(['organisme_id', 'budgets'],'Ce champ doit être rempli'); //$validator->allowEmptyString('organisme_id', false, 'Ce champ doit être rempli'); //$validator->allowEmptyString('budgets', false, 'Ce champ doit être rempli'); $validator->allowEmpty('lieu_detail')->add('lieu_detail', 'valid', [ 'rule' => 'check_string', 'message' => 'Ce champ contient des caractères interdits', 'provider' => 'table' ]); $validator //->allowEmptyString('nom_user', false, 'Ce champ est obligatoire') ->add('nom_user', 'valid', [ 'rule' => 'check_string', 'message' => 'Ce champ contient des caractères interdits', 'provider' => 'table' ]); //$validator->notEmpty('nom_responsable')->add('nom_responsable', 'valid', [ $validator->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->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'); /* * (EP 202109) Annulation des champs obligatoires définis comme tels ici ou dans le schéma de la BD * (afin de pouvoir les rendre éventuellement optionnels si on le désire via la configuration) */ $bd_mandatory_fields = [ // Champs définis comme obligatoires dans le schéma BD (pas bien...) 'budgets', // Champs définis comme obligatoires par défaut par l'utilisation de règles telles que "checkstring" ou autre (valid...) 'nom_user', 'nom_responsable', 'email_responsable', ]; foreach ($bd_mandatory_fields as $fname) $validator->allowEmpty($fname, true); //foreach ($bd_mandatory_fields as $fname) $validator->allowEmptyString($fname, true); /* * (EP 202109) Définition des champs obligatoires pour la création (et modif) d'une fiche matériel * selon le fichier de configuration (config/app_labinvent_mandatory_fields.yml) * * TODO: faire plutot dépendre cette liste du statut du matériel : * (solution ici: https://book.cakephp.org/4/en/core-libraries/validation.html#conditional-validation) * - CREATED => LOT0 * - TOBEORDERED => LOT1 * - VALIDATED (et plus) => LOT2 */ // - Par défaut, tous les champs du LOT 0 $mandatory_fields = $this->getMandatoryFieldsForLot(0); // - Par défaut, tous les champs du LOT 1 (plus contraignant) //$mandatory_fields = $this->getMandatoryFieldsForLot(1); $this->_setMandatoryFields($validator, $mandatory_fields); // Définition des champs qui doivent rester optionnels /* $optional_fields = array_diff_key(MANDATORY_FIELDS_FOR_LOT1, $mandatory_fields); //debug($optional_fields); foreach ($optional_fields as $fname) $validator->allowEmptyString($fname, true); */ /* * * TRES INSTRUCTIF * * debug($validator); * * => Affiche la liste des champs qui sont obligatoires : * object(Cake\Validation\Validator) { '_allowEmptyMessages' => [ 'designation' => 'Ce champ doit être rempli', 'description' => 'Ce champ doit être rempli', 'sur_categorie_id' => 'Ce champ doit être rempli', 'categorie_id' => 'Ce champ doit être rempli' ], ... */ //debug($validator); return $validator; } // validationDefault() private function _setMandatoryFields(Validator $validator, array $fields) { // (EP 31/5/21) Champs obligatoires (LOT1) foreach ($fields as $fname=>$fdisp) { // Champs spéciaux dont le caractère obligatoire est géré indirectement (via beforeSave()) // (fournisseur_id, et autres champs ajoutés dans l'avenir ? ...) if ($fname=='fournisseur_id') continue; // Champs virtuels (n'existent pas physiquement) if (strtoupper($fname)=='DEVIS') continue; $validator->allowEmptyString($fname, false, 'Ce champ doit être rempli'); } } public function validationLOT1(Validator $validator) { $validator = $this->validationDefault($validator); //$this->_setMandatoryFields( $validator, $this->getMandatoryFieldsLot1() ); //$validator->add('password', 'length', ['rule' => ['lengthBetween', 8, 100]]); return $validator; } public function validationLOT2(Validator $validator) { $validator = $this->validationDefault($validator); $this->_setMandatoryFields( $validator, $this->getMandatoryFieldsForLot(2) ); //$validator->add('password', 'length', ['rule' => ['lengthBetween', 8, 100]]); return $validator; } 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(); $this->LAST_SEUIL_INVENTORIABLE = $this->config->prix_inventaire_administratif; // 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) { //debug($entity->date_acquisition); //debug($entity->date_acquisition->format('Y')); $year = $entity->date_acquisition->format('Y'); $this->SEUIL_INVENTORIABLE = ($year >= $this->LAST_SEUIL_INVENTORIABLE_YEAR) ? $this->LAST_SEUIL_INVENTORIABLE : $this->PREV_SEUIL_INVENTORIABLE; return ($entity->prix_ht >= $this->SEUIL_INVENTORIABLE); // 1000€ pour l'IRAP (depuis 2017 ?) //return ($entity->prix_ht !== null); } return true; }; /* // Check prix > 0 $checkPriceIsStrictPositive = function (Entity $entity) { if ($entity->prix_ht) return $entity->prix_ht > 0; return true; }; $checkPriceIsNumeric = function (Entity $entity) { if ($entity->prix_ht) return numeric($entity->prix_ht); 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.' ]); */ /* (EP 202010 n'est plus nécessaire) $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 HT doit obligatoirement être supérieur à ". $this->LAST_SEUIL_INVENTORIABLE."€ (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 HT doit obligatoirement être supérieur à ".$this->LAST_SEUIL_INVENTORIABLE."€ (inventoriable)" ]); */ /* //if (! IP2I) $rules->add($checkPriceIsStrictPositive, [ $rules->add($checkPriceIsStrictPositive, [ 'errorField' => 'prix_ht', //'message' => 'Le matériel ne peut pas être inventoriable et ne pas avoir de prix' 'message' => "Le montant doit être supérieur à 0 €" ]); */ $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 : $new_date_year = $entity->date_acquisition; // Pas de date saisie (nulle) => return false (ne pas traiter) if (! $new_date_year) return false; //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; if (is_null($old_date_year)) return true; $old_date_year = $old_date_year->format('Y'); $new_date_year = $new_date_year->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) { // Si date achat donnée, on prend son année, sinon on prend l'année en cours //$year=substr($entity->get('date_acquisition'), 6, 4); //debug(new \Cake\Chronos\Date()); //$tz = new \DateTimeZone('Europe/Paris'); //$date = $today = new \DateTime('now',$tz); //$now = new \DateTime('now'); //$date_achat = $DATE_GIVEN ? substr($entity->get('date_acquisition') : (new FrozenDate('now'))->format('yy'); $date = $DATE_GIVEN ? $entity->get('date_acquisition') : new FrozenDate('now'); //debug($date); $year = $date->year; // yyyy /////$year = $DATE_GIVEN ? substr($entity->get('date_acquisition'), 6, 4) : now(); //$year = $date->format('Y'); // yyyy //$year = $date->format('y'); // yy //debug($year); /* ////debug("year before : $year"); if (strlen($year) == 2) { $year = '20' . $year; } */ ////debug("year after: $year"); $numero_laboratoire .= '-' . $year; //debug($numero_laboratoire); } //$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(); $num = $num ? $num['numero_laboratoire'] : 'XXXX-0000'; //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); //debug($entity->numero_laboratoire); //$entity->setError('jkl', 'toto'); } // date achat changée } // not 'administrer' 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']; $fournisseur_asis = $entity->fournisseur ? $entity->fournisseur['name'] : ''; // Enlever les espaces superflus $fournisseur = trim($fournisseur_asis); //debug($fournisseur); //debug($entity); // Si champ fournisseur obligatoire, vérification qu'il est bien rempli (ou emission d'une erreur) //if (IP2I && $entity->fournisseur && $fournisseur == '') { if ( // - fournisseur_id est obligatoire pour le statut courant du matos //in_array( 'fournisseur_id', array_keys($this->getMandatoryFieldsLot1()) ) in_array( 'fournisseur_id', array_keys($this->getMandatoryFieldsForMaterielStatus($entity->status)) ) // - et on est en mode add ou edit (le champ fournisseur existe) && $entity->fournisseur // - et le champ fournisseur est vide && $fournisseur == '' ) { //debug("champ fournisseur vide !");exit; //$msgError1 = "Pour valider un matériel, le champ suivant ne doit pas être vide : ".'fourniss'.' du matériel'; //$this->Flash->error($msgError1); /* MARCHE PAS POURQUOI ? $materiel->setError($field, 'Ce champ ne doit pas être vide'); $materiel->setError('numero_commande', 'Ce champ ne doit pas être vide'); */ $entity->setError('fournisseur', 'Ce champ ne doit pas être vide'); return false; } //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) //debug($entity); exit; unset($entity->fournisseur_orig); unset($entity->fournisseur); //debug($entity); exit; // TRES IMPORTANT, sinon echec de la sauvegarde !!! return true; } }