stocktransferline.class.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. <?php
  2. /* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
  3. * Copyright (C) 2021 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
  4. * Copyright (C) ---Put here your own copyright and developer email---
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. /**
  20. * \file class/stocktransferline.class.php
  21. * \ingroup stocktransfer
  22. * \brief This file is a CRUD class file for StockTransferLine (Create/Read/Update/Delete)
  23. */
  24. // Put here all includes required by your class file
  25. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
  26. //require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
  27. //require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
  28. /**
  29. * Class for StockTransferLine
  30. */
  31. class StockTransferLine extends CommonObjectLine
  32. {
  33. /**
  34. * @var string ID to identify managed object.
  35. */
  36. public $element = 'stocktransferline';
  37. /**
  38. * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management.
  39. */
  40. public $table_element = 'stocktransfer_stocktransferline';
  41. /**
  42. * @var int Does this object support multicompany module ?
  43. * 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
  44. */
  45. public $ismultientitymanaged = 0;
  46. /**
  47. * @var int Does object support extrafields ? 0=No, 1=Yes
  48. */
  49. public $isextrafieldmanaged = 1;
  50. /**
  51. * @var string String with name of icon for stocktransferline. Must be the part after the 'object_' into object_stocktransferline.png
  52. */
  53. public $picto = 'stocktransferline@stocktransfer';
  54. const STATUS_DRAFT = 0;
  55. const STATUS_VALIDATED = 1;
  56. const STATUS_CANCELED = 9;
  57. /**
  58. * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
  59. * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
  60. * 'label' the translation key.
  61. * 'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM)
  62. * 'position' is the sort order of field.
  63. * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
  64. * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
  65. * 'noteditable' says if field is not editable (1 or 0)
  66. * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
  67. * 'index' if we want an index in database.
  68. * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
  69. * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
  70. * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
  71. * 'css' is the CSS style to use on field. For example: 'maxwidth200'
  72. * 'help' is a string visible as a tooltip on field
  73. * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
  74. * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
  75. * 'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
  76. * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
  77. * 'comment' is not used. You can store here any text of your choice. It is not used by application.
  78. *
  79. * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
  80. */
  81. // BEGIN MODULEBUILDER PROPERTIES
  82. /**
  83. * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
  84. */
  85. public $fields=array(
  86. 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>'1', 'position'=>1, 'notnull'=>1, 'visible'=>0, 'noteditable'=>'1', 'index'=>1, 'comment'=>"Id"),
  87. 'amount' => array('type'=>'price', 'label'=>'Amount', 'enabled'=>'1', 'position'=>40, 'notnull'=>0, 'visible'=>1, 'default'=>'null', 'isameasure'=>'1', 'help'=>"Help text for amount",),
  88. 'qty' => array('type'=>'real', 'label'=>'Qty', 'enabled'=>'1', 'position'=>45, 'notnull'=>0, 'visible'=>1, 'default'=>'0', 'isameasure'=>'1', 'css'=>'maxwidth75imp', 'help'=>"Help text for quantity",),
  89. 'fk_warehouse_destination' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Entrepôt de destination', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>1,),
  90. 'fk_warehouse_source' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Entrepôt source', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>1,),
  91. 'fk_stocktransfer' => array('type'=>'integer:StockTransfer:stocktransfer/stock/class/stocktransfer.class.php', 'label'=>'StockTransfer', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>0,),
  92. 'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>1,),
  93. 'batch' => array('type'=>'varchar(128)', 'label'=>'Batch', 'enabled'=>'1', 'position'=>1000, 'notnull'=>-1, 'visible'=>1,),
  94. 'pmp' => array('type'=>'double'/*, 'help'=>'THMEstimatedHelp'*/, 'label'=>'PMP', 'enabled'=>'1', 'position'=>50, 'notnull'=>0, 'visible'=>1,),
  95. 'rang' => array('type'=>'integer', 'label'=>'Qty', 'enabled'=>'1', 'position'=>45, 'notnull'=>0, 'visible'=>0, 'default'=>'0', 'isameasure'=>'1', 'css'=>'maxwidth75imp', 'help'=>"Help text for quantity",),
  96. );
  97. public $rowid;
  98. public $amount;
  99. public $qty;
  100. public $fk_warehouse_destination;
  101. public $fk_warehouse_source;
  102. public $fk_stocktransfer;
  103. public $fk_product;
  104. public $batch;
  105. // END MODULEBUILDER PROPERTIES
  106. // If this object has a subtable with lines
  107. /**
  108. * @var int Name of subtable line
  109. */
  110. //public $table_element_line = 'stocktransfer_stocktransferlineline';
  111. /**
  112. * @var int Field with ID of parent key if this object has a parent
  113. */
  114. //public $fk_element = 'fk_stocktransferline';
  115. /**
  116. * @var int Name of subtable class that manage subtable lines
  117. */
  118. //public $class_element_line = 'StockTransferLineline';
  119. /**
  120. * @var array List of child tables. To test if we can delete object.
  121. */
  122. //protected $childtables = array();
  123. /**
  124. * @var array List of child tables. To know object to delete on cascade.
  125. * If name matches '@ClassNAme:FilePathClass;ParentFkFieldName' it will
  126. * call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object
  127. */
  128. //protected $childtablesoncascade = array('stocktransfer_stocktransferlinedet');
  129. /**
  130. * @var StockTransferLineLine[] Array of subtable lines
  131. */
  132. //public $lines = array();
  133. /**
  134. * Constructor
  135. *
  136. * @param DoliDb $db Database handler
  137. */
  138. public function __construct(DoliDB $db)
  139. {
  140. global $conf, $langs;
  141. $this->db = $db;
  142. if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) $this->fields['rowid']['visible'] = 0;
  143. if (!isModEnabled('multicompany') && isset($this->fields['entity'])) $this->fields['entity']['enabled'] = 0;
  144. // Example to show how to set values of fields definition dynamically
  145. /*if ($user->rights->stocktransfer->stocktransferline->read) {
  146. $this->fields['myfield']['visible'] = 1;
  147. $this->fields['myfield']['noteditable'] = 0;
  148. }*/
  149. // Unset fields that are disabled
  150. foreach ($this->fields as $key => $val) {
  151. if (isset($val['enabled']) && empty($val['enabled'])) {
  152. unset($this->fields[$key]);
  153. }
  154. }
  155. // Translate some data of arrayofkeyval
  156. if (is_object($langs)) {
  157. foreach ($this->fields as $key => $val) {
  158. if (is_array($val['arrayofkeyval'])) {
  159. foreach ($val['arrayofkeyval'] as $key2 => $val2) {
  160. $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
  161. }
  162. }
  163. }
  164. }
  165. }
  166. /**
  167. * Create object into database
  168. *
  169. * @param User $user User that creates
  170. * @param bool $notrigger false=launch triggers after, true=disable triggers
  171. * @return int <0 if KO, Id of created object if OK
  172. */
  173. public function create(User $user, $notrigger = false)
  174. {
  175. return $this->createCommon($user, $notrigger);
  176. }
  177. /**
  178. * Clone an object into another one
  179. *
  180. * @param User $user User that creates
  181. * @param int $fromid Id of object to clone
  182. * @return mixed New object created, <0 if KO
  183. */
  184. public function createFromClone(User $user, $fromid)
  185. {
  186. global $langs, $extrafields;
  187. $error = 0;
  188. dol_syslog(__METHOD__, LOG_DEBUG);
  189. $object = new self($this->db);
  190. $this->db->begin();
  191. // Load source object
  192. $result = $object->fetchCommon($fromid);
  193. if ($result > 0 && !empty($object->table_element_line)) $object->fetchLines();
  194. // get lines so they will be clone
  195. //foreach($this->lines as $line)
  196. // $line->fetch_optionals();
  197. // Reset some properties
  198. unset($object->id);
  199. unset($object->import_key);
  200. // Clear fields
  201. $object->ref = empty($this->fields['ref']['default']) ? "copy_of_".$object->ref : $this->fields['ref']['default'];
  202. $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
  203. $object->status = self::STATUS_DRAFT;
  204. // ...
  205. // Clear extrafields that are unique
  206. if (is_array($object->array_options) && count($object->array_options) > 0) {
  207. $extrafields->fetch_name_optionals_label($this->table_element);
  208. foreach ($object->array_options as $key => $option) {
  209. $shortkey = preg_replace('/options_/', '', $key);
  210. if (!empty($extrafields->attributes[$this->table_element]['unique'][$shortkey])) {
  211. //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
  212. unset($object->array_options[$key]);
  213. }
  214. }
  215. }
  216. // Create clone
  217. $object->context['createfromclone'] = 'createfromclone';
  218. $result = $object->createCommon($user);
  219. if ($result < 0) {
  220. $error++;
  221. $this->error = $object->error;
  222. $this->errors = $object->errors;
  223. }
  224. if (!$error) {
  225. // copy internal contacts
  226. if ($this->copy_linked_contact($object, 'internal') < 0) {
  227. $error++;
  228. }
  229. }
  230. if (!$error) {
  231. // copy external contacts if same company
  232. if (property_exists($this, 'socid') && $this->socid == $object->socid) {
  233. if ($this->copy_linked_contact($object, 'external') < 0)
  234. $error++;
  235. }
  236. }
  237. unset($object->context['createfromclone']);
  238. // End
  239. if (!$error) {
  240. $this->db->commit();
  241. return $object;
  242. } else {
  243. $this->db->rollback();
  244. return -1;
  245. }
  246. }
  247. /**
  248. * Load object in memory from the database
  249. *
  250. * @param int $id Id object
  251. * @param string $ref Ref
  252. * @return int <0 if KO, 0 if not found, >0 if OK
  253. */
  254. public function fetch($id, $ref = null)
  255. {
  256. $result = $this->fetchCommon($id, $ref);
  257. if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
  258. return $result;
  259. }
  260. /**
  261. * Load object lines in memory from the database
  262. *
  263. * @return int <0 if KO, 0 if not found, >0 if OK
  264. */
  265. public function fetchLines()
  266. {
  267. $this->lines = array();
  268. $result = $this->fetchLinesCommon();
  269. return $result;
  270. }
  271. /**
  272. * Load list of objects in memory from the database.
  273. *
  274. * @param string $sortorder Sort Order
  275. * @param string $sortfield Sort field
  276. * @param int $limit limit
  277. * @param int $offset Offset
  278. * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...)
  279. * @param string $filtermode Filter mode (AND or OR)
  280. * @return array|int int <0 if KO, array of pages if OK
  281. */
  282. public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
  283. {
  284. global $conf;
  285. dol_syslog(__METHOD__, LOG_DEBUG);
  286. $records = array();
  287. $sql = 'SELECT ';
  288. $sql .= $this->getFieldList();
  289. $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
  290. if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
  291. else $sql .= ' WHERE 1 = 1';
  292. // Manage filter
  293. $sqlwhere = array();
  294. if (count($filter) > 0) {
  295. foreach ($filter as $key => $value) {
  296. if ($key == 't.rowid') {
  297. $sqlwhere[] = $key.'='.$value;
  298. } elseif (strpos($key, 'date') !== false) {
  299. $sqlwhere[] = $key.' = \''.$this->db->idate($value).'\'';
  300. } elseif ($key == 'customsql') {
  301. $sqlwhere[] = $value;
  302. } else {
  303. $sqlwhere[] = $key.' LIKE \'%'.$this->db->escape($value).'%\'';
  304. }
  305. }
  306. }
  307. if (count($sqlwhere) > 0) {
  308. $sql .= " AND (".implode(" ".$filtermode." ", $sqlwhere).")";
  309. }
  310. if (!empty($sortfield)) {
  311. $sql .= $this->db->order($sortfield, $sortorder);
  312. }
  313. if (!empty($limit)) {
  314. $sql .= ' '.$this->db->plimit($limit, $offset);
  315. }
  316. $resql = $this->db->query($sql);
  317. if ($resql) {
  318. $num = $this->db->num_rows($resql);
  319. $i = 0;
  320. while ($i < ($limit ? min($limit, $num) : $num)) {
  321. $obj = $this->db->fetch_object($resql);
  322. $record = new self($this->db);
  323. $record->setVarsFromFetchObj($obj);
  324. $records[$record->id] = $record;
  325. $i++;
  326. }
  327. $this->db->free($resql);
  328. return $records;
  329. } else {
  330. $this->errors[] = 'Error '.$this->db->lasterror();
  331. dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
  332. return -1;
  333. }
  334. }
  335. /**
  336. * Update object into database
  337. *
  338. * @param User $user User that modifies
  339. * @param bool $notrigger false=launch triggers after, true=disable triggers
  340. * @return int <0 if KO, >0 if OK
  341. */
  342. public function update(User $user, $notrigger = false)
  343. {
  344. return $this->updateCommon($user, $notrigger);
  345. }
  346. /**
  347. * Delete object in database
  348. *
  349. * @param User $user User that deletes
  350. * @param bool $notrigger false=launch triggers after, true=disable triggers
  351. * @return int <0 if KO, >0 if OK
  352. */
  353. public function delete(User $user, $notrigger = false)
  354. {
  355. return $this->deleteCommon($user, $notrigger);
  356. //return $this->deleteCommon($user, $notrigger, 1);
  357. }
  358. /**
  359. * Delete a line of object in database
  360. *
  361. * @param User $user User that delete
  362. * @param int $idline Id of line to delete
  363. * @param bool $notrigger false=launch triggers after, true=disable triggers
  364. * @return int >0 if OK, <0 if KO
  365. */
  366. public function deleteLine(User $user, $idline, $notrigger = false)
  367. {
  368. if ($this->status < 0) {
  369. $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
  370. return -2;
  371. }
  372. return $this->deleteLineCommon($user, $idline, $notrigger);
  373. }
  374. /**
  375. * Makes all stock movements (add quantity, remove quantity or cancel all actions)
  376. *
  377. * @param string $label label of stock movement
  378. * @param string $code_inv label of stock movement
  379. * @param int $fk_entrepot Warehouse concerned by stock movement
  380. * @param int $direction add or remove qty
  381. * @return int 1 if ok, <= 0 if ko
  382. */
  383. public function doStockMovement($label, $code_inv, $fk_entrepot, $direction = 1)
  384. {
  385. global $conf, $user, $langs;
  386. require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
  387. include_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
  388. include_once DOL_DOCUMENT_ROOT . '/product/stock/stocktransfer/class/stocktransfer.class.php';
  389. $p = new Product($this->db);
  390. $p->fetch($this->fk_product);
  391. $op[0] = "+".trim($this->qty);
  392. $op[1] = "-".trim($this->qty);
  393. $movementstock = new MouvementStock($this->db);
  394. $st = new StockTransfer($this->db);
  395. $movementstock->origin_type = $st->origin_type;
  396. $movementstock->origin_id = $this->fk_stocktransfer;
  397. if (empty($this->batch)) { // no batch for line
  398. /*$result = $p->correct_stock(
  399. $user,
  400. $fk_entrepot,
  401. $this->qty,
  402. $direction, // 1=décrémentation
  403. $label,
  404. empty($direction) ? $this->pmp : 0,
  405. GETPOST('inventorycode', 'alphanohtml'),
  406. 'stocktransfer',
  407. $this->fk_stocktransfer
  408. );*/
  409. $result = $movementstock->_create($user,
  410. $p->id,
  411. $fk_entrepot,
  412. $op[$direction],
  413. $direction,
  414. empty($direction) ? $this->pmp : 0,
  415. $label,
  416. $code_inv);
  417. if ($result < 0) {
  418. setEventMessages($p->errors, $p->errorss, 'errors');
  419. return 0;
  420. }
  421. } else {
  422. if ($p->hasbatch()) {
  423. $arraybatchinfo = $p->loadBatchInfo($this->batch);
  424. if (count($arraybatchinfo) > 0) {
  425. $firstrecord = array_shift($arraybatchinfo);
  426. $dlc = $firstrecord['eatby'];
  427. $dluo = $firstrecord['sellby'];
  428. //var_dump($batch); var_dump($arraybatchinfo); var_dump($firstrecord); var_dump($dlc); var_dump($dluo); exit;
  429. } else {
  430. $dlc = '';
  431. $dluo = '';
  432. }
  433. /*$result = $p->correct_stock_batch(
  434. $user,
  435. $fk_entrepot,
  436. $this->qty,
  437. $direction,
  438. $label,
  439. empty($direction) ? $this->pmp : 0,
  440. $dlc,
  441. $dluo,
  442. $this->batch,
  443. GETPOST("codemove")
  444. );*/
  445. $result = $movementstock->_create($user,
  446. $p->id,
  447. $fk_entrepot,
  448. $op[$direction],
  449. $direction,
  450. empty($direction) ? $this->pmp : 0,
  451. $label,
  452. $code_inv,
  453. '',
  454. $dlc,
  455. $dluo,
  456. $this->batch);
  457. if ($result < 0) {
  458. setEventMessages($p->errors, $p->errorss, 'errors');
  459. return 0;
  460. }
  461. } else {
  462. setEventMessages($langs->trans('StockTransferNoBatchForProduct', $p->getNomUrl()), '', 'errors');
  463. return -1;
  464. }
  465. }
  466. return 1;
  467. }
  468. /**
  469. * Validate object
  470. *
  471. * @param User $user User making status change
  472. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  473. * @return int <=0 if OK, 0=Nothing done, >0 if KO
  474. */
  475. public function validate($user, $notrigger = 0)
  476. {
  477. global $conf, $langs;
  478. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  479. $error = 0;
  480. // Protection
  481. if ($this->status == self::STATUS_VALIDATED) {
  482. dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
  483. return 0;
  484. }
  485. /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->stocktransferline->write))
  486. || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->stocktransferline->stocktransferline_advance->validate))))
  487. {
  488. $this->error='NotEnoughPermissions';
  489. dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
  490. return -1;
  491. }*/
  492. $now = dol_now();
  493. $this->db->begin();
  494. // Define new ref
  495. if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
  496. $num = $this->getNextNumRef();
  497. } else {
  498. $num = $this->ref;
  499. }
  500. $this->newref = $num;
  501. if (!empty($num)) {
  502. // Validate
  503. $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
  504. $sql .= " SET ref = '".$this->db->escape($num)."',";
  505. $sql .= " status = ".self::STATUS_VALIDATED;
  506. if (!empty($this->fields['date_validation'])) $sql .= ", date_validation = '".$this->db->idate($now)."',";
  507. if (!empty($this->fields['fk_user_valid'])) $sql .= ", fk_user_valid = ".((int) $user->id);
  508. $sql .= " WHERE rowid = ".((int) $this->id);
  509. dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
  510. $resql = $this->db->query($sql);
  511. if (!$resql) {
  512. dol_print_error($this->db);
  513. $this->error = $this->db->lasterror();
  514. $error++;
  515. }
  516. if (!$error && !$notrigger) {
  517. // Call trigger
  518. $result = $this->call_trigger('STOCKTRANSFERLINE_VALIDATE', $user);
  519. if ($result < 0) $error++;
  520. // End call triggers
  521. }
  522. }
  523. if (!$error) {
  524. $this->oldref = $this->ref;
  525. // Rename directory if dir was a temporary ref
  526. if (preg_match('/^[\(]?PROV/i', $this->ref)) {
  527. // Now we rename also files into index
  528. $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'stocktransferline/".$this->db->escape($this->newref)."'";
  529. $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'stocktransferline/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
  530. $resql = $this->db->query($sql);
  531. if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
  532. // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
  533. $oldref = dol_sanitizeFileName($this->ref);
  534. $newref = dol_sanitizeFileName($num);
  535. $dirsource = $conf->stocktransfer->dir_output.'/stocktransferline/'.$oldref;
  536. $dirdest = $conf->stocktransfer->dir_output.'/stocktransferline/'.$newref;
  537. if (!$error && file_exists($dirsource)) {
  538. dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
  539. if (@rename($dirsource, $dirdest)) {
  540. dol_syslog("Rename ok");
  541. // Rename docs starting with $oldref with $newref
  542. $listoffiles = dol_dir_list($conf->stocktransfer->dir_output.'/stocktransferline/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
  543. foreach ($listoffiles as $fileentry) {
  544. $dirsource = $fileentry['name'];
  545. $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
  546. $dirsource = $fileentry['path'].'/'.$dirsource;
  547. $dirdest = $fileentry['path'].'/'.$dirdest;
  548. @rename($dirsource, $dirdest);
  549. }
  550. }
  551. }
  552. }
  553. }
  554. // Set new ref and current status
  555. if (!$error) {
  556. $this->ref = $num;
  557. $this->status = self::STATUS_VALIDATED;
  558. }
  559. if (!$error) {
  560. $this->db->commit();
  561. return 1;
  562. } else {
  563. $this->db->rollback();
  564. return -1;
  565. }
  566. }
  567. /**
  568. * Set draft status
  569. *
  570. * @param User $user Object user that modify
  571. * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
  572. * @return int <0 if KO, >0 if OK
  573. */
  574. public function setDraft($user, $notrigger = 0)
  575. {
  576. // Protection
  577. if ($this->status <= self::STATUS_DRAFT) {
  578. return 0;
  579. }
  580. /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->write))
  581. || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
  582. {
  583. $this->error='Permission denied';
  584. return -1;
  585. }*/
  586. return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'STOCKTRANSFERLINE_UNVALIDATE');
  587. }
  588. /**
  589. * Set cancel status
  590. *
  591. * @param User $user Object user that modify
  592. * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
  593. * @return int <0 if KO, 0=Nothing done, >0 if OK
  594. */
  595. public function cancel($user, $notrigger = 0)
  596. {
  597. // Protection
  598. if ($this->status != self::STATUS_VALIDATED) {
  599. return 0;
  600. }
  601. /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->write))
  602. || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
  603. {
  604. $this->error='Permission denied';
  605. return -1;
  606. }*/
  607. return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'STOCKTRANSFERLINE_CLOSE');
  608. }
  609. /**
  610. * Set back to validated status
  611. *
  612. * @param User $user Object user that modify
  613. * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
  614. * @return int <0 if KO, 0=Nothing done, >0 if OK
  615. */
  616. public function reopen($user, $notrigger = 0)
  617. {
  618. // Protection
  619. if ($this->status != self::STATUS_CANCELED) {
  620. return 0;
  621. }
  622. /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->write))
  623. || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
  624. {
  625. $this->error='Permission denied';
  626. return -1;
  627. }*/
  628. return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'STOCKTRANSFERLINE_REOPEN');
  629. }
  630. /**
  631. * Return a link to the object card (with optionaly the picto)
  632. *
  633. * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
  634. * @param string $option On what the link point to ('nolink', ...)
  635. * @param int $notooltip 1=Disable tooltip
  636. * @param string $morecss Add more css on link
  637. * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
  638. * @return string String with URL
  639. */
  640. public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
  641. {
  642. global $conf, $langs, $hookmanager;
  643. if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
  644. $result = '';
  645. $label = '<u>'.$langs->trans("StockTransferLine").'</u>';
  646. $label .= '<br>';
  647. $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
  648. if (isset($this->status)) {
  649. $label .= '<br><b>'.$langs->trans("Status").":</b> ".$this->getLibStatut(5);
  650. }
  651. $url = dol_buildpath('/stocktransfer/stocktransferline_card.php', 1).'?id='.$this->id;
  652. if ($option != 'nolink') {
  653. // Add param to save lastsearch_values or not
  654. $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
  655. if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
  656. if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
  657. }
  658. $linkclose = '';
  659. if (empty($notooltip)) {
  660. if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
  661. $label = $langs->trans("ShowStockTransferLine");
  662. $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
  663. }
  664. $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
  665. $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
  666. } else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
  667. $linkstart = '<a href="'.$url.'"';
  668. $linkstart .= $linkclose.'>';
  669. $linkend = '</a>';
  670. $result .= $linkstart;
  671. if (empty($this->showphoto_on_popup)) {
  672. if ($withpicto) $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
  673. } else {
  674. if ($withpicto) {
  675. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  676. list($class, $module) = explode('@', $this->picto);
  677. $upload_dir = $conf->$module->multidir_output[$conf->entity]."/$class/".dol_sanitizeFileName($this->ref);
  678. $filearray = dol_dir_list($upload_dir, "files");
  679. $filename = $filearray[0]['name'];
  680. if (!empty($filename)) {
  681. $pospoint = strpos($filearray[0]['name'], '.');
  682. $pathtophoto = $class.'/'.$this->ref.'/thumbs/'.substr($filename, 0, $pospoint).'_mini'.substr($filename, $pospoint);
  683. if (empty($conf->global->{strtoupper($module.'_'.$class).'_FORMATLISTPHOTOSASUSERS'})) {
  684. $result .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref"><img class="photo'.$module.'" alt="No photo" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$module.'&entity='.$conf->entity.'&file='.urlencode($pathtophoto).'"></div></div>';
  685. } else {
  686. $result .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photouserphoto userphoto" alt="No photo" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$module.'&entity='.$conf->entity.'&file='.urlencode($pathtophoto).'"></div>';
  687. }
  688. $result .= '</div>';
  689. } else {
  690. $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
  691. }
  692. }
  693. }
  694. if ($withpicto != 2) $result .= $this->ref;
  695. $result .= $linkend;
  696. //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
  697. global $action, $hookmanager;
  698. $hookmanager->initHooks(array('stocktransferlinedao'));
  699. $parameters = array('id'=>$this->id, 'getnomurl'=>$result);
  700. $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  701. if ($reshook > 0) $result = $hookmanager->resPrint;
  702. else $result .= $hookmanager->resPrint;
  703. return $result;
  704. }
  705. /**
  706. * Return label of the status
  707. *
  708. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
  709. * @return string Label of status
  710. */
  711. public function getLibStatut($mode = 0)
  712. {
  713. return $this->LibStatut($this->status, $mode);
  714. }
  715. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  716. /**
  717. * Return the status
  718. *
  719. * @param int $status Id status
  720. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
  721. * @return string Label of status
  722. */
  723. public function LibStatut($status, $mode = 0)
  724. {
  725. // phpcs:enable
  726. if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
  727. global $langs;
  728. //$langs->load("stocktransfer@stocktransfer");
  729. $this->labelStatus[self::STATUS_DRAFT] = $langs->trans('Draft');
  730. $this->labelStatus[self::STATUS_VALIDATED] = $langs->trans('Enabled');
  731. $this->labelStatus[self::STATUS_CANCELED] = $langs->trans('Disabled');
  732. $this->labelStatusShort[self::STATUS_DRAFT] = $langs->trans('Draft');
  733. $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->trans('Enabled');
  734. $this->labelStatusShort[self::STATUS_CANCELED] = $langs->trans('Disabled');
  735. }
  736. $statusType = 'status'.$status;
  737. //if ($status == self::STATUS_VALIDATED) $statusType = 'status1';
  738. if ($status == self::STATUS_CANCELED) $statusType = 'status6';
  739. return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
  740. }
  741. /**
  742. * Load the info information in the object
  743. *
  744. * @param int $id Id of object
  745. * @return void
  746. */
  747. public function info($id)
  748. {
  749. $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
  750. $sql .= ' fk_user_creat, fk_user_modif';
  751. $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
  752. $sql .= ' WHERE t.rowid = '.((int) $id);
  753. $result = $this->db->query($sql);
  754. if ($result) {
  755. if ($this->db->num_rows($result)) {
  756. $obj = $this->db->fetch_object($result);
  757. $this->id = $obj->rowid;
  758. $this->user_creation_id = $obj->fk_user_creat;
  759. $this->user_modification_id = $obj->fk_user_modif;
  760. $this->date_creation = $this->db->jdate($obj->datec);
  761. $this->date_modification = empty($obj->datem) ? '' : $this->db->jdate($obj->datem);
  762. }
  763. $this->db->free($result);
  764. } else {
  765. dol_print_error($this->db);
  766. }
  767. }
  768. /**
  769. * Initialise object with example values
  770. * Id must be 0 if object instance is a specimen
  771. *
  772. * @return void
  773. */
  774. public function initAsSpecimen()
  775. {
  776. $this->initAsSpecimenCommon();
  777. }
  778. /**
  779. * Create an array of lines
  780. *
  781. * @return array|int array of lines if OK, <0 if KO
  782. */
  783. public function getLinesArray()
  784. {
  785. $this->lines = array();
  786. $objectline = new StockTransferLineLine($this->db);
  787. $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_stocktransferline = '.((int) $this->id)));
  788. if (is_numeric($result)) {
  789. $this->error = $objectline->error;
  790. $this->errors = $objectline->errors;
  791. return $result;
  792. } else {
  793. $this->lines = $result;
  794. return $this->lines;
  795. }
  796. }
  797. /**
  798. * Returns the reference to the following non used object depending on the active numbering module.
  799. *
  800. * @return string Object free reference
  801. */
  802. public function getNextNumRef()
  803. {
  804. global $langs, $conf;
  805. $langs->load("stocks");
  806. if (empty($conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON)) {
  807. $conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON = 'mod_stocktransferline_standard';
  808. }
  809. if (!empty($conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON)) {
  810. $mybool = false;
  811. $file = $conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON.".php";
  812. $classname = $conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON;
  813. // Include file with class
  814. $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
  815. foreach ($dirmodels as $reldir) {
  816. $dir = dol_buildpath($reldir."core/modules/stocktransfer/");
  817. // Load file with numbering class (if found)
  818. $mybool |= @include_once $dir.$file;
  819. }
  820. if ($mybool === false) {
  821. dol_print_error('', "Failed to include file ".$file);
  822. return '';
  823. }
  824. if (class_exists($classname)) {
  825. $obj = new $classname();
  826. $numref = $obj->getNextValue($this);
  827. if ($numref != '' && $numref != '-1') {
  828. return $numref;
  829. } else {
  830. $this->error = $obj->error;
  831. //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
  832. return "";
  833. }
  834. } else {
  835. print $langs->trans("Error")." ".$langs->trans("ClassNotFound").' '.$classname;
  836. return "";
  837. }
  838. } else {
  839. print $langs->trans("ErrorNumberingModuleNotSetup", $this->element);
  840. return "";
  841. }
  842. }
  843. /**
  844. * Create a document onto disk according to template module.
  845. *
  846. * @param string $modele Force template to use ('' to not force)
  847. * @param Translate $outputlangs objet lang a utiliser pour traduction
  848. * @param int $hidedetails Hide details of lines
  849. * @param int $hidedesc Hide description
  850. * @param int $hideref Hide ref
  851. * @param null|array $moreparams Array to provide more information
  852. * @return int 0 if KO, 1 if OK
  853. */
  854. public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
  855. {
  856. global $conf, $langs;
  857. $result = 0;
  858. $includedocgeneration = 0;
  859. $langs->load("stocks");
  860. if (!dol_strlen($modele)) {
  861. $modele = 'standard_stocktransferline';
  862. if ($this->modelpdf) {
  863. $modele = $this->modelpdf;
  864. } elseif (!empty($conf->global->STOCKTRANSFERLINE_ADDON_PDF)) {
  865. $modele = $conf->global->STOCKTRANSFERLINE_ADDON_PDF;
  866. }
  867. }
  868. $modelpath = "core/modules/stocktransfer/doc/";
  869. if ($includedocgeneration) {
  870. $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
  871. }
  872. return $result;
  873. }
  874. /**
  875. * Action executed by scheduler
  876. * CAN BE A CRON TASK. In such a case, parameters come from the schedule job setup field 'Parameters'
  877. * Use public function doScheduledJob($param1, $param2, ...) to get parameters
  878. *
  879. * @return int 0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
  880. */
  881. public function doScheduledJob()
  882. {
  883. global $conf, $langs;
  884. //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
  885. $error = 0;
  886. $this->output = '';
  887. $this->error = '';
  888. dol_syslog(__METHOD__, LOG_DEBUG);
  889. $now = dol_now();
  890. $this->db->begin();
  891. // ...
  892. $this->db->commit();
  893. return $error;
  894. }
  895. }
  896. /**
  897. * Class StockTransferLineLine. You can also remove this and generate a CRUD class for lines objects.
  898. */
  899. class StockTransferLineLine
  900. {
  901. // To complete with content of an object StockTransferLineLine
  902. // We should have a field rowid, fk_stocktransferline and position
  903. /**
  904. * @var int Does object support extrafields ? 0=No, 1=Yes
  905. */
  906. public $isextrafieldmanaged = 0;
  907. /**
  908. * Constructor
  909. *
  910. * @param DoliDb $db Database handler
  911. */
  912. public function __construct(DoliDB $db)
  913. {
  914. $this->db = $db;
  915. }
  916. }