Client.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. /**
  3. * Define a client.
  4. * @author Charles DUBOIS
  5. * @category Plugin
  6. * @license MIT
  7. */
  8. class Client extends Entity{
  9. public $id;
  10. public $label; //Libellé (Texte)
  11. public $name; //Libellé (Texte)
  12. public $firstname; //Libellé (Texte)
  13. public $state; //Etat (Liste classique)
  14. public $pseudonym; //Pseudonyme, Acronyme... (Texte)
  15. public $code; //Code client (Texte)
  16. public $category; //Categorie client (Liste configurable)
  17. public $siret; //N° SIRET (Texte)
  18. public $comment; //Commentaire (Texte Long)
  19. public $slug; //Identifiant unique (Texte)
  20. public $type; //Type (Particulier ou Entreprise) (Liste classique)
  21. public $condition; //Condition (prospect ou client) (Liste classique)
  22. public $parent; //Client parent (Entier)
  23. public $job; //Métier (Texte)
  24. public $meta; //Meta (JSON)
  25. public $firm; //Etablissement (Entier)
  26. protected $TABLE_NAME = 'client';
  27. public $entityLabel = 'Client';
  28. public $fields = array(
  29. 'id' => array('type'=>'key', 'label' => 'Identifiant'),
  30. 'label' => array('type'=>'text', 'label' => 'Libellé'),
  31. 'name' => array('type'=>'text', 'label' => 'Nom'),
  32. 'firstname' => array('type'=>'text', 'label' => 'Prénom'),
  33. 'state' => array('type'=>'list', 'label' => 'Etat'),
  34. 'pseudonym' => array('type'=>'text', 'label' => 'Pseudonyme'),
  35. 'code' => array('type'=>'text', 'label' => 'Code client'),
  36. 'category' => array('type'=>'dictionary', 'label' => 'Catégorie','link'=>'class/Dictionary.class.php'),
  37. 'siret' => array('type'=>'text', 'label' => 'N° SIRET'),
  38. 'comment' => array('type'=>'textarea', 'label' => 'Commentaire'),
  39. 'slug' => array('type'=>'text', 'label' => 'Slug'),
  40. 'type' => array('type'=>'list', 'label' => 'Type (Particulier ou Entreprise)'),
  41. 'condition' => array('type'=>'list', 'label' => 'Condition (prospect ou client)'),
  42. 'parent' => array('type'=>'int', 'label' => 'Id client parent','link'=>'plugin/client/Client.class.php'),
  43. 'meta' => array('type'=>'textarea', 'label' => 'Meta'),
  44. 'job' => array('type'=>'text', 'label' => 'Métier'),
  45. 'firm' => array('type'=>'int', 'label' => 'Etablissement','link'=>'class/Firm.class.php')
  46. );
  47. //Colonnes indexées
  48. public $indexes = array('parent','category','type','firm');
  49. public function __construct(){
  50. parent::__construct();
  51. $this->state = self::ACTIVE;
  52. }
  53. public function save(){
  54. if(empty($this->slug)){
  55. $this->slug = slugify($this->label);
  56. for($i = 2;$i<1000;$i++){
  57. if(self::rowCount(array('slug'=>$this->slug)) == 0) break;
  58. $this->slug = slugify($this->label).'-'.$i;
  59. }
  60. }
  61. parent::save();
  62. if(empty($this->code)){
  63. $this->code = self::code($this);
  64. for($i = 2;$i<1000;$i++){
  65. if(self::rowCount(array('code'=>$this->code)) == 0) break;
  66. $this->code = self::code($this).'-'.$i;
  67. }
  68. $this->save();
  69. }
  70. }
  71. public function meta($key=null,$value=null){
  72. $meta = json_decode($this->meta,true);
  73. $meta = !$meta ? array(): $meta ;
  74. if(!isset($key) && !isset($value)) return $meta;
  75. if(!isset($value)) return isset($meta[$key])?$meta[$key]:'';
  76. $meta[$key] = $value;
  77. $this->meta = json_encode($meta);
  78. }
  79. public function condition(){
  80. return self::conditions($this->condition);
  81. }
  82. public function type(){
  83. return self::types($this->type);
  84. }
  85. public function dir(){
  86. return File::dir().'client'.SLASH.$this->id.SLASH;
  87. }
  88. public function logo($returnPath = false){
  89. $mask = $this->dir().'logo.*';
  90. $files = glob($mask);
  91. if($returnPath) return isset($files[0]) ? $files[0] : __DIR__.SLASH.'img'.SLASH.'default-logo.png';
  92. return isset($files[0]) ? 'action.php?action=client_assets_load&type=logo&client='.$this->id : 'action.php?action=client_assets_load&type=logo';
  93. }
  94. public function cover($returnPath = false){
  95. $mask = $this->dir().'cover.*';
  96. $files = glob($mask);
  97. if($returnPath) return isset($files[0]) ? $files[0] : __DIR__.SLASH.'img'.SLASH.'default-cover.jpg';
  98. return isset($files[0]) ? 'action.php?action=client_assets_load&type=cover&client='.$this->id : 'action.php?action=client_assets_load&type=cover';
  99. }
  100. //Vérification des doublons par libellés
  101. public static function check_duplicate($field,$label,$omitId = null){
  102. $rows = array();
  103. switch($field){
  104. case 'label':
  105. $query = 'SELECT * FROM {{table}} WHERE (soundex(?) = soundex(label) OR label=? OR label like ?)';
  106. $data = array($label,$label,$label);
  107. if(isset($omitId)){
  108. $query .= ' AND id!=?';
  109. $data[] = $omitId;
  110. }
  111. $regex = '/\s(d(es?|u)|l(a|es?)|une?|à)\s/i';
  112. $rows = self::staticQuery($query,$data,true);
  113. foreach ($rows as $key => $row) {
  114. $rows[$key] = $rows[$key]->toArray();
  115. //supression des articles définis/indefinis pour plus de pertinence
  116. $new = preg_replace($regex,' ',mb_strtolower($label));
  117. $existing = preg_replace($regex,'',mb_strtolower($rows[$key]['label']));
  118. $rows[$key]['levenshtein'] = levenshtein($existing,$new);
  119. if($rows[$key]['levenshtein']>3) unset($rows[$key]);
  120. }
  121. //tri par levenstein
  122. usort($rows,function($a,$b){
  123. return $a['levenshtein'] > $b['levenshtein'];
  124. });
  125. break;
  126. case 'mail':
  127. case 'phone':
  128. $type = $field == 'mail' ? Contact::PROFESSIONAL_MAIL : Contact::MOBILE;
  129. foreach(Contact::loadAll(array(
  130. 'type' => $type,
  131. 'state' => Contact::ACTIVE,
  132. 'value' => trim($label),
  133. 'scope' => 'client'
  134. )) as $item){
  135. $client = Client::getById($item->uid);
  136. $row = $client->toArray();
  137. $row['levenshtein'] = 1;
  138. $rows[] = $row;
  139. }
  140. break;
  141. case 'siret':
  142. foreach(Client::loadAll(array(
  143. 'siret' => trim($label),
  144. 'state' => Contact::ACTIVE
  145. )) as $item){
  146. $row = $item->toArray();
  147. $row['levenshtein'] = 1;
  148. $rows[] = $row;
  149. }
  150. break;
  151. default:
  152. return;
  153. break;
  154. }
  155. return $rows;
  156. }
  157. //Fusionne le client spécifié avec l'instance de client depuis lequel est appellé la méthode
  158. // le tableau keeping définis quel coté écrase l'autre dans le cas ou les deux clients sont renseignés (left = instance, right = parametre client)
  159. public function merge($client,$keeping = array()){
  160. $logs = array('Fusion du client '.$client->label().' (#'.$client->id.') sur '.$this->label().' (#'.$this->id.') ');
  161. Plugin::callHook('client_merge',array($this,$client,&$logs));
  162. foreach ($keeping as $key => $value) {
  163. if(!empty($this->$key) && empty($client->$key)){
  164. $logs[] = $key.' origine : renseigné, fusionné : non renseigné';
  165. continue;
  166. }
  167. if($this->$key == $client->$key){
  168. $logs[] = $key.' origine et fusionné identiques';
  169. continue;
  170. }
  171. if(empty($this->$key) && !empty($client->$key)){
  172. $this->$key = $client->$key;
  173. $logs[] = $key.' origine : non renseigné, fusionné : renseigné';
  174. continue;
  175. }
  176. if(!empty($this->$key) && !empty($client->$key)){
  177. $logs[] = $key.' origine : renseigné, fusionné : renseigné, utilisation du tableau d\'ecrasement';
  178. if($value == 'left') continue;
  179. if($value == 'right') $this->$key = $client->$key;
  180. }
  181. }
  182. $logs[] = 'Sauvegarde du client d\'origine';
  183. $this->save();
  184. $logs[] = 'Suppression du client fusionné';
  185. self::delete(array('id'=>$client->id));
  186. return $logs;
  187. }
  188. public static function conditions($key = null){
  189. global $conf;
  190. $conditions = array(
  191. 'prospect' => array('label' =>'Prospect'),
  192. 'client' => array('label' => ucFirst($conf->get('client_label_singular'))),
  193. );
  194. if(!isset($key)) return $conditions;
  195. return isset($conditions[$key]) ? $conditions[$key] : array('label' =>'Inconnu');
  196. }
  197. public static function types($key = null){
  198. $types = array(
  199. 'individual' => array('label' =>'Personne'),
  200. 'firm' => array('label' =>'Structure'),
  201. );
  202. if(!isset($key)) return $types;
  203. return isset($types[$key]) ? $types[$key] : array('label' =>'Inconnu');
  204. }
  205. public static function addressType($key = null){
  206. $types = array(
  207. 'global' => array('label' =>'Général','icon'=>'far fa-building'),
  208. 'invoice' => array('label' =>'Facturation','icon'=>'fas fa-file-invoice-dollar'),
  209. 'shipping' => array('label' =>'Livraison','icon'=>'fas fa-shipping-fast')
  210. );
  211. Plugin::callHook('client_address_type',array($types) );
  212. if(!isset($key)) return $types;
  213. return isset($types[$key]) ? $types[$key] : array('label' =>'Inconnu');
  214. }
  215. public static function internals($key = null){
  216. $internals = array(
  217. 'commercial' => array('label' =>'Commercial'),
  218. 'technician' => array('label' =>'Technicien')
  219. );
  220. if(!isset($key)) return $internals;
  221. return isset($internals[$key]) ? $internals[$key] : array('label' =>'Inconnu');
  222. }
  223. public static function code($client){
  224. global $conf;
  225. $mask = empty($conf->get('client_code_mask')) ? '{{label[2]}}-{{Y}}{{M}}-{{id}}' : $conf->get('client_code_mask');
  226. $data = $client->toArray();
  227. $data['label'] = $client->label();
  228. $data['M'] = date('m');
  229. $data['m'] = date('m');
  230. $data['Y'] = date('Y');
  231. $data['y'] = date('y');
  232. $mask = preg_replace_callback('/{{label\[([0-9]*)\]}}/is', function($matches) use ($client){
  233. if(count($matches)>=1){
  234. return strlen($client->label>=$matches[1]) ? substr($client->label, 0,$matches[1]) : $client->label;
  235. }else{
  236. return $matches[0];
  237. }
  238. }, $mask);
  239. $mask = preg_replace_callback('/{{rand\(([0-9]*),([0-9]*)\)}}/is', function($matches){
  240. if(count($matches)>=2){
  241. return mt_rand($matches[1],$matches[2]);
  242. }else{
  243. return $matches[0];
  244. }
  245. }, $mask);
  246. $value = template($mask,$data,true);
  247. return strtoupper(slugify($value));
  248. }
  249. //Récupere les adresses du client, de son parent, de ses enfants et de ses frêres
  250. public function addresses($type = null,$order='zip',$limit = null, $parent = false, $siblings = false, $childs = false){
  251. $queryFilters = '';
  252. $data = array();
  253. if ($parent) {
  254. $queryFilters .= ' OR id=(SELECT parent FROM client WHERE id=?) ';
  255. $data[] = $this->id;
  256. }
  257. if ($siblings) {
  258. $queryFilters .= ' OR parent = (SELECT parent FROM client WHERE id=?) ';
  259. $data[] = $this->id;
  260. }
  261. if ($childs) {
  262. $queryFilters .= ' OR parent = ? ';
  263. $data[] = $this->id;
  264. }
  265. $query = 'SELECT address.*, client.label FROM `address`
  266. LEFT JOIN client ON address.uid = client.id
  267. WHERE uid IN (SELECT id FROM client WHERE id = ? '.$queryFilters.' ) AND scope="client" ';
  268. $data[] = $this->id;
  269. if(isset($type))
  270. $query.= ' AND address.type ="'.$type.'" ';
  271. if(isset($order))
  272. $query.= ' ORDER BY '.$order;
  273. if(isset($limit))
  274. $query.= ' LIMIT '.$limit;
  275. //todo gérer + de 1 niveau
  276. $address = Address::staticQuery($query,$data,true);
  277. if (isset($order) && $order == 'fullname') {
  278. usort($address,function($a,$b){
  279. if($a->fullName() == $b->fullName()) return 0;
  280. return $a->fullName() > $b->fullName() ? 1 :-1 ;
  281. });
  282. }
  283. return $address;
  284. }
  285. public function contacts($key=null){
  286. $contacts = array();
  287. $filters = array('uid'=>$this->id,'scope'=>'client') ;
  288. if(isset($key)) $filters['type'] = $key;
  289. foreach(Contact::loadAll($filters) as $contact){
  290. $contacts[$contact->type] = $contact;
  291. }
  292. if(!isset($key)) return $contacts;
  293. return isset($contacts[$key]) ? $contacts[$key] : new Contact();
  294. }
  295. public function subsites(){
  296. return $this->id!=0 ? Client::loadAll(array('parent'=>$this->id)) : array();
  297. }
  298. public function holding(){
  299. return $this->id!=0 && $this->parent!=0 ? Client::getById($this->parent) : new Client();
  300. }
  301. public function label(){
  302. return $this->type=='individual' ? $this->firstname.' '.$this->name : $this->label;
  303. }
  304. public function remove($recursive = false){
  305. $this->state = Client::INACTIVE;
  306. $this->save();
  307. if($recursive){
  308. foreach(Client::loadAll(array('parent'=>$this->id,'state:!='=>Client::INACTIVE)) as $subclient){
  309. $subclient->remove();
  310. }
  311. }
  312. global $myFirm;
  313. //Gestion des champs dynamiques
  314. if($myFirm->has_plugin('fr.core.dynamicform')){
  315. Plugin::need('dynamicform/DynamicForm');
  316. Dynamicform::remove('client-sheet-custom',array(
  317. 'scope'=>'client',
  318. 'uid'=>$this->id
  319. ));
  320. }
  321. ContactPerson::change(array('state'=>Client::INACTIVE),array('scope'=>'client','uid'=>$this->id));
  322. Address::delete(array('scope'=>'client','uid'=>$this->id));
  323. }
  324. }
  325. ?>