expedition.class.php 84 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923
  1. <?php
  2. /* Copyright (C) 2003-2008 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
  4. * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
  5. * Copyright (C) 2006-2012 Laurent Destailleur <eldy@users.sourceforge.net>
  6. * Copyright (C) 2011-2017 Juanjo Menent <jmenent@2byte.es>
  7. * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
  8. * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
  9. * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
  10. * Copyright (C) 2014-2017 Francis Appels <francis.appels@yahoo.com>
  11. * Copyright (C) 2015 Claudio Aschieri <c.aschieri@19.coop>
  12. * Copyright (C) 2016 Ferran Marcet <fmarcet@2byte.es>
  13. * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
  14. * Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr>
  15. *
  16. * This program is free software; you can redistribute it and/or modify
  17. * it under the terms of the GNU General Public License as published by
  18. * the Free Software Foundation; either version 3 of the License, or
  19. * (at your option) any later version.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU General Public License
  27. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  28. */
  29. /**
  30. * \file htdocs/expedition/class/expedition.class.php
  31. * \ingroup expedition
  32. * \brief Fichier de la classe de gestion des expeditions
  33. */
  34. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
  35. require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
  36. if (! empty($conf->propal->enabled)) require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
  37. if (! empty($conf->commande->enabled)) require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
  38. if (! empty($conf->productbatch->enabled)) require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
  39. /**
  40. * Class to manage shipments
  41. */
  42. class Expedition extends CommonObject
  43. {
  44. /**
  45. * @var string ID to identify managed object
  46. */
  47. public $element="shipping";
  48. /**
  49. * @var int Field with ID of parent key if this field has a parent
  50. */
  51. public $fk_element="fk_expedition";
  52. /**
  53. * @var string Name of table without prefix where object is stored
  54. */
  55. public $table_element="expedition";
  56. /**
  57. * @var int Name of subtable line
  58. */
  59. public $table_element_line="expeditiondet";
  60. /**
  61. * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  62. * @var int
  63. */
  64. public $ismultientitymanaged = 1;
  65. /**
  66. * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
  67. */
  68. public $picto = 'sending';
  69. public $socid;
  70. /**
  71. * @var string Customer ref
  72. */
  73. public $ref_customer;
  74. /**
  75. * @var string internal ref
  76. */
  77. public $ref_int;
  78. public $brouillon;
  79. /**
  80. * @var int warehouse id
  81. */
  82. public $entrepot_id;
  83. public $lines=array();
  84. /**
  85. * @var string Tracking number
  86. */
  87. public $tracking_number;
  88. /**
  89. * @var string Tracking url
  90. */
  91. public $tracking_url;
  92. public $billed;
  93. /**
  94. * @var string name of pdf model
  95. */
  96. public $model_pdf;
  97. public $trueWeight;
  98. public $weight_units;
  99. public $trueWidth;
  100. public $width_units;
  101. public $trueHeight;
  102. public $height_units;
  103. public $trueDepth;
  104. public $depth_units;
  105. // A denormalized value
  106. public $trueSize;
  107. public $date_delivery; // Date delivery planed
  108. /**
  109. * @deprecated
  110. * @see $date_shipping
  111. */
  112. public $date;
  113. /**
  114. * @deprecated
  115. * @see $date_shipping
  116. */
  117. public $date_expedition;
  118. /**
  119. * Effective delivery date
  120. * @var int
  121. */
  122. public $date_shipping;
  123. public $date_creation;
  124. public $date_valid;
  125. public $meths;
  126. public $listmeths; // List of carriers
  127. /**
  128. * Draft status
  129. */
  130. const STATUS_DRAFT = 0;
  131. /**
  132. * Validated status
  133. */
  134. const STATUS_VALIDATED = 1;
  135. /**
  136. * Closed status
  137. */
  138. const STATUS_CLOSED = 2;
  139. /**
  140. * Constructor
  141. *
  142. * @param DoliDB $db Database handler
  143. */
  144. public function __construct($db)
  145. {
  146. global $conf;
  147. $this->db = $db;
  148. $this->lines = array();
  149. $this->products = array();
  150. // List of long language codes for status
  151. $this->statuts = array();
  152. $this->statuts[-1] = 'StatusSendingCanceled';
  153. $this->statuts[0] = 'StatusSendingDraft';
  154. $this->statuts[1] = 'StatusSendingValidated';
  155. $this->statuts[2] = 'StatusSendingProcessed';
  156. // List of short language codes for status
  157. $this->statutshorts = array();
  158. $this->statutshorts[-1] = 'StatusSendingCanceledShort';
  159. $this->statutshorts[0] = 'StatusSendingDraftShort';
  160. $this->statutshorts[1] = 'StatusSendingValidatedShort';
  161. $this->statutshorts[2] = 'StatusSendingProcessedShort';
  162. /* Status "billed" or not is managed by another field than status
  163. if (! empty($conf->global->WORKFLOW_BILL_ON_SHIPMENT))
  164. {
  165. $this->statuts[2] = 'StatusSendingBilled';
  166. $this->statutshorts[2] = 'StatusSendingBilledShort';
  167. }*/
  168. }
  169. /**
  170. * Return next contract ref
  171. *
  172. * @param Societe $soc Thirdparty object
  173. * @return string Free reference for contract
  174. */
  175. public function getNextNumRef($soc)
  176. {
  177. global $langs, $conf;
  178. $langs->load("sendings");
  179. if (!empty($conf->global->EXPEDITION_ADDON_NUMBER))
  180. {
  181. $mybool = false;
  182. $file = $conf->global->EXPEDITION_ADDON_NUMBER.".php";
  183. $classname = $conf->global->EXPEDITION_ADDON_NUMBER;
  184. // Include file with class
  185. $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
  186. foreach ($dirmodels as $reldir) {
  187. $dir = dol_buildpath($reldir."core/modules/expedition/");
  188. // Load file with numbering class (if found)
  189. $mybool|=@include_once $dir.$file;
  190. }
  191. if (! $mybool)
  192. {
  193. dol_print_error('', "Failed to include file ".$file);
  194. return '';
  195. }
  196. $obj = new $classname();
  197. $numref = "";
  198. $numref = $obj->getNextValue($soc, $this);
  199. if ( $numref != "")
  200. {
  201. return $numref;
  202. }
  203. else
  204. {
  205. dol_print_error($this->db, get_class($this)."::getNextNumRef ".$obj->error);
  206. return "";
  207. }
  208. }
  209. else
  210. {
  211. print $langs->trans("Error")." ".$langs->trans("Error_EXPEDITION_ADDON_NUMBER_NotDefined");
  212. return "";
  213. }
  214. }
  215. /**
  216. * Create expedition en base
  217. *
  218. * @param User $user Objet du user qui cree
  219. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  220. * @return int <0 si erreur, id expedition creee si ok
  221. */
  222. public function create($user, $notrigger = 0)
  223. {
  224. global $conf, $hookmanager;
  225. $now=dol_now();
  226. require_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php';
  227. $error = 0;
  228. // Clean parameters
  229. $this->brouillon = 1;
  230. $this->tracking_number = dol_sanitizeFileName($this->tracking_number);
  231. if (empty($this->fk_project)) $this->fk_project = 0;
  232. $this->user = $user;
  233. $this->db->begin();
  234. $sql = "INSERT INTO ".MAIN_DB_PREFIX."expedition (";
  235. $sql.= "ref";
  236. $sql.= ", entity";
  237. $sql.= ", ref_customer";
  238. $sql.= ", ref_int";
  239. $sql.= ", date_creation";
  240. $sql.= ", fk_user_author";
  241. $sql.= ", date_expedition";
  242. $sql.= ", date_delivery";
  243. $sql.= ", fk_soc";
  244. $sql.= ", fk_projet";
  245. $sql.= ", fk_address";
  246. $sql.= ", fk_shipping_method";
  247. $sql.= ", tracking_number";
  248. $sql.= ", weight";
  249. $sql.= ", size";
  250. $sql.= ", width";
  251. $sql.= ", height";
  252. $sql.= ", weight_units";
  253. $sql.= ", size_units";
  254. $sql.= ", note_private";
  255. $sql.= ", note_public";
  256. $sql.= ", model_pdf";
  257. $sql.= ", fk_incoterms, location_incoterms";
  258. $sql.= ") VALUES (";
  259. $sql.= "'(PROV)'";
  260. $sql.= ", ".$conf->entity;
  261. $sql.= ", ".($this->ref_customer?"'".$this->db->escape($this->ref_customer)."'":"null");
  262. $sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
  263. $sql.= ", '".$this->db->idate($now)."'";
  264. $sql.= ", ".$user->id;
  265. $sql.= ", ".($this->date_expedition>0?"'".$this->db->idate($this->date_expedition)."'":"null");
  266. $sql.= ", ".($this->date_delivery>0?"'".$this->db->idate($this->date_delivery)."'":"null");
  267. $sql.= ", ".$this->socid;
  268. $sql.= ", ".$this->fk_project;
  269. $sql.= ", ".($this->fk_delivery_address>0?$this->fk_delivery_address:"null");
  270. $sql.= ", ".($this->shipping_method_id>0?$this->shipping_method_id:"null");
  271. $sql.= ", '".$this->db->escape($this->tracking_number)."'";
  272. $sql.= ", ".$this->weight;
  273. $sql.= ", ".$this->sizeS; // TODO Should use this->trueDepth
  274. $sql.= ", ".$this->sizeW; // TODO Should use this->trueWidth
  275. $sql.= ", ".$this->sizeH; // TODO Should use this->trueHeight
  276. $sql.= ", ".($this->weight_units != '' ? (int) $this->weight_units : 'NULL');
  277. $sql.= ", ".($this->size_units != '' ? (int) $this->size_units : 'NULL');
  278. $sql.= ", ".(!empty($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null");
  279. $sql.= ", ".(!empty($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null");
  280. $sql.= ", ".(!empty($this->model_pdf)?"'".$this->db->escape($this->model_pdf)."'":"null");
  281. $sql.= ", ".(int) $this->fk_incoterms;
  282. $sql.= ", '".$this->db->escape($this->location_incoterms)."'";
  283. $sql.= ")";
  284. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  285. $resql=$this->db->query($sql);
  286. if ($resql)
  287. {
  288. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expedition");
  289. $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
  290. $sql.= " SET ref = '(PROV".$this->id.")'";
  291. $sql.= " WHERE rowid = ".$this->id;
  292. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  293. if ($this->db->query($sql))
  294. {
  295. // Insertion des lignes
  296. $num=count($this->lines);
  297. for ($i = 0; $i < $num; $i++)
  298. {
  299. if (! isset($this->lines[$i]->detail_batch))
  300. { // no batch management
  301. if (! $this->create_line($this->lines[$i]->entrepot_id, $this->lines[$i]->origin_line_id, $this->lines[$i]->qty, $this->lines[$i]->array_options) > 0)
  302. {
  303. $error++;
  304. }
  305. }
  306. else
  307. { // with batch management
  308. if (! $this->create_line_batch($this->lines[$i], $this->lines[$i]->array_options) > 0)
  309. {
  310. $error++;
  311. }
  312. }
  313. }
  314. if (! $error && $this->id && $this->origin_id)
  315. {
  316. $ret = $this->add_object_linked();
  317. if (!$ret)
  318. {
  319. $error++;
  320. }
  321. }
  322. // Actions on extra fields
  323. if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))
  324. {
  325. $result=$this->insertExtraFields();
  326. if ($result < 0)
  327. {
  328. $error++;
  329. }
  330. }
  331. if (! $error && ! $notrigger)
  332. {
  333. // Call trigger
  334. $result=$this->call_trigger('SHIPPING_CREATE', $user);
  335. if ($result < 0) { $error++; }
  336. // End call triggers
  337. if (! $error)
  338. {
  339. $this->db->commit();
  340. return $this->id;
  341. }
  342. else
  343. {
  344. foreach($this->errors as $errmsg)
  345. {
  346. dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
  347. $this->error.=($this->error?', '.$errmsg:$errmsg);
  348. }
  349. $this->db->rollback();
  350. return -1*$error;
  351. }
  352. }
  353. else
  354. {
  355. $error++;
  356. $this->error=$this->db->lasterror()." - sql=$sql";
  357. $this->db->rollback();
  358. return -3;
  359. }
  360. }
  361. else
  362. {
  363. $error++;
  364. $this->error=$this->db->lasterror()." - sql=$sql";
  365. $this->db->rollback();
  366. return -2;
  367. }
  368. }
  369. else
  370. {
  371. $error++;
  372. $this->error=$this->db->error()." - sql=$sql";
  373. $this->db->rollback();
  374. return -1;
  375. }
  376. }
  377. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  378. /**
  379. * Create a expedition line
  380. *
  381. * @param int $entrepot_id Id of warehouse
  382. * @param int $origin_line_id Id of source line
  383. * @param int $qty Quantity
  384. * @param array $array_options extrafields array
  385. * @return int <0 if KO, line_id if OK
  386. */
  387. public function create_line($entrepot_id, $origin_line_id, $qty, $array_options = 0)
  388. {
  389. //phpcs:enable
  390. global $user;
  391. $expeditionline = new ExpeditionLigne($this->db);
  392. $expeditionline->fk_expedition = $this->id;
  393. $expeditionline->entrepot_id = $entrepot_id;
  394. $expeditionline->fk_origin_line = $origin_line_id;
  395. $expeditionline->qty = $qty;
  396. $expeditionline->array_options = $array_options;
  397. if (($lineId = $expeditionline->insert($user)) < 0)
  398. {
  399. $this->errors[]=$expeditionline->error;
  400. }
  401. return $lineId;
  402. }
  403. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  404. /**
  405. * Create the detail (eat-by date) of the expedition line
  406. *
  407. * @param object $line_ext full line informations
  408. * @param array $array_options extrafields array
  409. * @return int <0 if KO, >0 if OK
  410. */
  411. public function create_line_batch($line_ext, $array_options = 0)
  412. {
  413. // phpcs:enable
  414. $error = 0;
  415. $stockLocationQty = array(); // associated array with batch qty in stock location
  416. $tab=$line_ext->detail_batch;
  417. // create stockLocation Qty array
  418. foreach ($tab as $detbatch)
  419. {
  420. if ($detbatch->entrepot_id)
  421. {
  422. $stockLocationQty[$detbatch->entrepot_id] += $detbatch->qty;
  423. }
  424. }
  425. // create shipment lines
  426. foreach ($stockLocationQty as $stockLocation => $qty)
  427. {
  428. if (($line_id = $this->create_line($stockLocation, $line_ext->origin_line_id, $qty, $array_options)) < 0)
  429. {
  430. $error++;
  431. }
  432. else
  433. {
  434. // create shipment batch lines for stockLocation
  435. foreach ($tab as $detbatch)
  436. {
  437. if ($detbatch->entrepot_id == $stockLocation){
  438. if (! ($detbatch->create($line_id) >0)) // Create an expeditionlinebatch
  439. {
  440. $error++;
  441. }
  442. }
  443. }
  444. }
  445. }
  446. if (! $error) return 1;
  447. else return -1;
  448. }
  449. /**
  450. * Get object and lines from database
  451. *
  452. * @param int $id Id of object to load
  453. * @param string $ref Ref of object
  454. * @param string $ref_ext External reference of object
  455. * @param string $ref_int Internal reference of other object
  456. * @return int >0 if OK, 0 if not found, <0 if KO
  457. */
  458. public function fetch($id, $ref = '', $ref_ext = '', $ref_int = '')
  459. {
  460. global $conf;
  461. // Check parameters
  462. if (empty($id) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1;
  463. $sql = "SELECT e.rowid, e.entity, e.ref, e.fk_soc as socid, e.date_creation, e.ref_customer, e.ref_ext, e.ref_int, e.fk_user_author, e.fk_statut, e.fk_projet as fk_project, e.billed";
  464. $sql.= ", e.date_valid";
  465. $sql.= ", e.weight, e.weight_units, e.size, e.size_units, e.width, e.height";
  466. $sql.= ", e.date_expedition as date_expedition, e.model_pdf, e.fk_address, e.date_delivery";
  467. $sql.= ", e.fk_shipping_method, e.tracking_number";
  468. $sql.= ", e.note_private, e.note_public";
  469. $sql.= ', e.fk_incoterms, e.location_incoterms';
  470. $sql.= ', i.libelle as libelle_incoterms';
  471. $sql.= ', s.libelle as shipping_method';
  472. $sql.= ", el.fk_source as origin_id, el.sourcetype as origin";
  473. $sql.= " FROM ".MAIN_DB_PREFIX."expedition as e";
  474. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = e.rowid AND el.targettype = '".$this->db->escape($this->element)."'";
  475. $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON e.fk_incoterms = i.rowid';
  476. $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_shipment_mode as s ON e.fk_shipping_method = s.rowid';
  477. $sql.= " WHERE e.entity IN (".getEntity('expedition').")";
  478. if ($id) $sql.= " AND e.rowid=".$id;
  479. if ($ref) $sql.= " AND e.ref='".$this->db->escape($ref)."'";
  480. if ($ref_ext) $sql.= " AND e.ref_ext='".$this->db->escape($ref_ext)."'";
  481. if ($ref_int) $sql.= " AND e.ref_int='".$this->db->escape($ref_int)."'";
  482. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  483. $result = $this->db->query($sql);
  484. if ($result)
  485. {
  486. if ($this->db->num_rows($result))
  487. {
  488. $obj = $this->db->fetch_object($result);
  489. $this->id = $obj->rowid;
  490. $this->entity = $obj->entity;
  491. $this->ref = $obj->ref;
  492. $this->socid = $obj->socid;
  493. $this->ref_customer = $obj->ref_customer;
  494. $this->ref_ext = $obj->ref_ext;
  495. $this->ref_int = $obj->ref_int;
  496. $this->statut = $obj->fk_statut;
  497. $this->user_author_id = $obj->fk_user_author;
  498. $this->date_creation = $this->db->jdate($obj->date_creation);
  499. $this->date_valid = $this->db->jdate($obj->date_valid);
  500. $this->date = $this->db->jdate($obj->date_expedition); // TODO deprecated
  501. $this->date_expedition = $this->db->jdate($obj->date_expedition); // TODO deprecated
  502. $this->date_shipping = $this->db->jdate($obj->date_expedition); // Date real
  503. $this->date_delivery = $this->db->jdate($obj->date_delivery); // Date planed
  504. $this->fk_delivery_address = $obj->fk_address;
  505. $this->modelpdf = $obj->model_pdf;
  506. $this->shipping_method_id = $obj->fk_shipping_method;
  507. $this->shipping_method = $obj->shipping_method;
  508. $this->tracking_number = $obj->tracking_number;
  509. $this->origin = ($obj->origin?$obj->origin:'commande'); // For compatibility
  510. $this->origin_id = $obj->origin_id;
  511. $this->billed = $obj->billed;
  512. $this->fk_project = $obj->fk_project;
  513. $this->trueWeight = $obj->weight;
  514. $this->weight_units = $obj->weight_units;
  515. $this->trueWidth = $obj->width;
  516. $this->width_units = $obj->size_units;
  517. $this->trueHeight = $obj->height;
  518. $this->height_units = $obj->size_units;
  519. $this->trueDepth = $obj->size;
  520. $this->depth_units = $obj->size_units;
  521. $this->note_public = $obj->note_public;
  522. $this->note_private = $obj->note_private;
  523. // A denormalized value
  524. $this->trueSize = $obj->size."x".$obj->width."x".$obj->height;
  525. $this->size_units = $obj->size_units;
  526. //Incoterms
  527. $this->fk_incoterms = $obj->fk_incoterms;
  528. $this->location_incoterms = $obj->location_incoterms;
  529. $this->libelle_incoterms = $obj->libelle_incoterms;
  530. $this->db->free($result);
  531. if ($this->statut == self::STATUS_DRAFT) $this->brouillon = 1;
  532. // Tracking url
  533. $this->getUrlTrackingStatus($obj->tracking_number);
  534. /*
  535. * Thirdparty
  536. */
  537. $result=$this->fetch_thirdparty();
  538. // Retreive extrafields
  539. $this->fetch_optionals();
  540. /*
  541. * Lines
  542. */
  543. $result=$this->fetch_lines();
  544. if ($result < 0)
  545. {
  546. return -3;
  547. }
  548. return 1;
  549. }
  550. else
  551. {
  552. dol_syslog(get_class($this).'::Fetch no expedition found', LOG_ERR);
  553. $this->error='Delivery with id '.$id.' not found';
  554. return 0;
  555. }
  556. }
  557. else
  558. {
  559. $this->error=$this->db->error();
  560. return -1;
  561. }
  562. }
  563. /**
  564. * Validate object and update stock if option enabled
  565. *
  566. * @param User $user Object user that validate
  567. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  568. * @return int <0 if OK, >0 if KO
  569. */
  570. public function valid($user, $notrigger = 0)
  571. {
  572. global $conf, $langs;
  573. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  574. dol_syslog(get_class($this)."::valid");
  575. // Protection
  576. if ($this->statut)
  577. {
  578. dol_syslog(get_class($this)."::valid no draft status", LOG_WARNING);
  579. return 0;
  580. }
  581. if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->expedition->creer))
  582. || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->expedition->shipping_advance->validate))))
  583. {
  584. $this->error='Permission denied';
  585. dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
  586. return -1;
  587. }
  588. $this->db->begin();
  589. $error = 0;
  590. // Define new ref
  591. $soc = new Societe($this->db);
  592. $soc->fetch($this->socid);
  593. // Class of company linked to order
  594. $result=$soc->set_as_client();
  595. // Define new ref
  596. if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
  597. {
  598. $numref = $this->getNextNumRef($soc);
  599. }
  600. else
  601. {
  602. $numref = "EXP".$this->id;
  603. }
  604. $this->newref = $numref;
  605. $now=dol_now();
  606. // Validate
  607. $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
  608. $sql.= " ref='".$numref."'";
  609. $sql.= ", fk_statut = 1";
  610. $sql.= ", date_valid = '".$this->db->idate($now)."'";
  611. $sql.= ", fk_user_valid = ".$user->id;
  612. $sql.= " WHERE rowid = ".$this->id;
  613. dol_syslog(get_class($this)."::valid update expedition", LOG_DEBUG);
  614. $resql=$this->db->query($sql);
  615. if (! $resql)
  616. {
  617. $this->error=$this->db->lasterror();
  618. $error++;
  619. }
  620. // If stock increment is done on sending (recommanded choice)
  621. if (! $error && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT))
  622. {
  623. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
  624. $langs->load("agenda");
  625. // Loop on each product line to add a stock movement
  626. $sql = "SELECT cd.fk_product, cd.subprice,";
  627. $sql.= " ed.rowid, ed.qty, ed.fk_entrepot,";
  628. $sql.= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
  629. $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
  630. $sql.= " ".MAIN_DB_PREFIX."expeditiondet as ed";
  631. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
  632. $sql.= " WHERE ed.fk_expedition = ".$this->id;
  633. $sql.= " AND cd.rowid = ed.fk_origin_line";
  634. dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
  635. $resql=$this->db->query($sql);
  636. if ($resql)
  637. {
  638. $cpt = $this->db->num_rows($resql);
  639. for ($i = 0; $i < $cpt; $i++)
  640. {
  641. $obj = $this->db->fetch_object($resql);
  642. if (empty($obj->edbrowid))
  643. {
  644. $qty = $obj->qty;
  645. }
  646. else
  647. {
  648. $qty = $obj->edbqty;
  649. }
  650. if ($qty <= 0) continue;
  651. dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
  652. //var_dump($this->lines[$i]);
  653. $mouvS = new MouvementStock($this->db);
  654. $mouvS->origin = &$this;
  655. if (empty($obj->edbrowid))
  656. {
  657. // line without batch detail
  658. // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record.
  659. $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref));
  660. if ($result < 0) {
  661. $error++;
  662. $this->error = $mouvS->error;
  663. $this->errors = array_merge($this->errors, $mouvS->errors);
  664. break;
  665. }
  666. }
  667. else
  668. {
  669. // line with batch detail
  670. // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record.
  671. // Note: ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version)
  672. $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
  673. if ($result < 0) {
  674. $error++;
  675. $this->error = $mouvS->error;
  676. $this->errors = array_merge($this->errors, $mouvS->errors);
  677. break;
  678. }
  679. }
  680. }
  681. }
  682. else
  683. {
  684. $this->db->rollback();
  685. $this->error=$this->db->error();
  686. return -2;
  687. }
  688. }
  689. // Change status of order to "shipment in process"
  690. $ret = $this->setStatut(Commande::STATUS_SHIPMENTONPROCESS, $this->origin_id, $this->origin);
  691. if (! $ret)
  692. {
  693. $error++;
  694. }
  695. if (! $error && ! $notrigger)
  696. {
  697. // Call trigger
  698. $result=$this->call_trigger('SHIPPING_VALIDATE', $user);
  699. if ($result < 0) { $error++; }
  700. // End call triggers
  701. }
  702. if (! $error)
  703. {
  704. $this->oldref = $this->ref;
  705. // Rename directory if dir was a temporary ref
  706. if (preg_match('/^[\(]?PROV/i', $this->ref))
  707. {
  708. // Now we rename also files into index
  709. $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref)+1).")), filepath = 'expedition/sending/".$this->db->escape($this->newref)."'";
  710. $sql.= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'expedition/sending/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
  711. $resql = $this->db->query($sql);
  712. if (! $resql) { $error++; $this->error = $this->db->lasterror(); }
  713. // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
  714. $oldref = dol_sanitizeFileName($this->ref);
  715. $newref = dol_sanitizeFileName($numref);
  716. $dirsource = $conf->expedition->dir_output.'/sending/'.$oldref;
  717. $dirdest = $conf->expedition->dir_output.'/sending/'.$newref;
  718. if (! $error && file_exists($dirsource))
  719. {
  720. dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
  721. if (@rename($dirsource, $dirdest))
  722. {
  723. dol_syslog("Rename ok");
  724. // Rename docs starting with $oldref with $newref
  725. $listoffiles=dol_dir_list($conf->expedition->dir_output.'/sending/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
  726. foreach($listoffiles as $fileentry)
  727. {
  728. $dirsource=$fileentry['name'];
  729. $dirdest=preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
  730. $dirsource=$fileentry['path'].'/'.$dirsource;
  731. $dirdest=$fileentry['path'].'/'.$dirdest;
  732. @rename($dirsource, $dirdest);
  733. }
  734. }
  735. }
  736. }
  737. }
  738. // Set new ref and current status
  739. if (! $error)
  740. {
  741. $this->ref = $numref;
  742. $this->statut = self::STATUS_VALIDATED;
  743. }
  744. if (! $error)
  745. {
  746. $this->db->commit();
  747. return 1;
  748. }
  749. else
  750. {
  751. $this->db->rollback();
  752. return -1*$error;
  753. }
  754. }
  755. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  756. /**
  757. * Create a delivery receipt from a shipment
  758. *
  759. * @param User $user User
  760. * @return int <0 if KO, >=0 if OK
  761. */
  762. public function create_delivery($user)
  763. {
  764. // phpcs:enable
  765. global $conf;
  766. if ($conf->livraison_bon->enabled)
  767. {
  768. if ($this->statut == self::STATUS_VALIDATED || $this->statut == self::STATUS_CLOSED)
  769. {
  770. // Expedition validee
  771. include_once DOL_DOCUMENT_ROOT.'/livraison/class/livraison.class.php';
  772. $delivery = new Livraison($this->db);
  773. $result=$delivery->create_from_sending($user, $this->id);
  774. if ($result > 0)
  775. {
  776. return $result;
  777. }
  778. else
  779. {
  780. $this->error=$delivery->error;
  781. return $result;
  782. }
  783. }
  784. else return 0;
  785. }
  786. else return 0;
  787. }
  788. /**
  789. * Add an expedition line.
  790. * If STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS is set, you can add a shipment line, with no stock source defined
  791. * If STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT is not set, you can add a shipment line, even if not enough into stock
  792. *
  793. * @param int $entrepot_id Id of warehouse
  794. * @param int $id Id of source line (order line)
  795. * @param int $qty Quantity
  796. * @param array $array_options extrafields array
  797. * @return int <0 if KO, >0 if OK
  798. */
  799. public function addline($entrepot_id, $id, $qty, $array_options = 0)
  800. {
  801. global $conf, $langs;
  802. $num = count($this->lines);
  803. $line = new ExpeditionLigne($this->db);
  804. $line->entrepot_id = $entrepot_id;
  805. $line->origin_line_id = $id;
  806. $line->qty = $qty;
  807. $orderline = new OrderLine($this->db);
  808. $orderline->fetch($id);
  809. if (! empty($conf->stock->enabled) && ! empty($orderline->fk_product))
  810. {
  811. $fk_product = $orderline->fk_product;
  812. if (! ($entrepot_id > 0) && empty($conf->global->STOCK_WAREHOUSE_NOT_REQUIRED_FOR_SHIPMENTS))
  813. {
  814. $langs->load("errors");
  815. $this->error=$langs->trans("ErrorWarehouseRequiredIntoShipmentLine");
  816. return -1;
  817. }
  818. if ($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT)
  819. {
  820. // Check must be done for stock of product into warehouse if $entrepot_id defined
  821. $product=new Product($this->db);
  822. $result=$product->fetch($fk_product);
  823. if ($entrepot_id > 0) {
  824. $product->load_stock('warehouseopen');
  825. $product_stock = $product->stock_warehouse[$entrepot_id]->real;
  826. }
  827. else
  828. $product_stock = $product->stock_reel;
  829. $product_type=$product->type;
  830. if ($product_type == 0 && $product_stock < $qty)
  831. {
  832. $langs->load("errors");
  833. $this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
  834. $this->db->rollback();
  835. return -3;
  836. }
  837. }
  838. }
  839. // If product need a batch number, we should not have called this function but addline_batch instead.
  840. if (! empty($conf->productbatch->enabled) && ! empty($orderline->fk_product) && ! empty($orderline->product_tobatch))
  841. {
  842. $this->error='ADDLINE_WAS_CALLED_INSTEAD_OF_ADDLINEBATCH';
  843. return -4;
  844. }
  845. // extrafields
  846. if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options)>0) // For avoid conflicts if trigger used
  847. $line->array_options = $array_options;
  848. $this->lines[$num] = $line;
  849. }
  850. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  851. /**
  852. * Add a shipment line with batch record
  853. *
  854. * @param array $dbatch Array of value (key 'detail' -> Array, key 'qty' total quantity for line, key ix_l : original line index)
  855. * @param array $array_options extrafields array
  856. * @return int <0 if KO, >0 if OK
  857. */
  858. public function addline_batch($dbatch, $array_options = 0)
  859. {
  860. // phpcs:enable
  861. global $conf,$langs;
  862. $num = count($this->lines);
  863. if ($dbatch['qty']>0)
  864. {
  865. $line = new ExpeditionLigne($this->db);
  866. $tab=array();
  867. foreach ($dbatch['detail'] as $key=>$value)
  868. {
  869. if ($value['q']>0)
  870. {
  871. // $value['q']=qty to move
  872. // $value['id_batch']=id into llx_product_batch of record to move
  873. //var_dump($value);
  874. $linebatch = new ExpeditionLineBatch($this->db);
  875. $ret=$linebatch->fetchFromStock($value['id_batch']); // load serial, sellby, eatby
  876. if ($ret<0)
  877. {
  878. $this->error=$linebatch->error;
  879. return -1;
  880. }
  881. $linebatch->qty=$value['q'];
  882. $tab[]=$linebatch;
  883. if ($conf->global->STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT)
  884. {
  885. require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
  886. $prod_batch = new Productbatch($this->db);
  887. $prod_batch->fetch($value['id_batch']);
  888. if ($prod_batch->qty < $linebatch->qty)
  889. {
  890. $langs->load("errors");
  891. $this->errors[]=$langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $prod_batch->fk_product);
  892. dol_syslog(get_class($this)."::addline_batch error=Product ".$prod_batch->batch.": ".$this->errorsToString(), LOG_ERR);
  893. $this->db->rollback();
  894. return -1;
  895. }
  896. }
  897. //var_dump($linebatch);
  898. }
  899. }
  900. $line->entrepot_id = $linebatch->entrepot_id;
  901. $line->origin_line_id = $dbatch['ix_l'];
  902. $line->qty = $dbatch['qty'];
  903. $line->detail_batch=$tab;
  904. // extrafields
  905. if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($array_options) && count($array_options)>0) // For avoid conflicts if trigger used
  906. $line->array_options = $array_options;
  907. //var_dump($line);
  908. $this->lines[$num] = $line;
  909. return 1;
  910. }
  911. }
  912. /**
  913. * Update database
  914. *
  915. * @param User $user User that modify
  916. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  917. * @return int <0 if KO, >0 if OK
  918. */
  919. public function update($user = null, $notrigger = 0)
  920. {
  921. global $conf;
  922. $error=0;
  923. // Clean parameters
  924. if (isset($this->ref)) $this->ref=trim($this->ref);
  925. if (isset($this->entity)) $this->entity=trim($this->entity);
  926. if (isset($this->ref_customer)) $this->ref_customer=trim($this->ref_customer);
  927. if (isset($this->socid)) $this->socid=trim($this->socid);
  928. if (isset($this->fk_user_author)) $this->fk_user_author=trim($this->fk_user_author);
  929. if (isset($this->fk_user_valid)) $this->fk_user_valid=trim($this->fk_user_valid);
  930. if (isset($this->fk_delivery_address)) $this->fk_delivery_address=trim($this->fk_delivery_address);
  931. if (isset($this->shipping_method_id)) $this->shipping_method_id=trim($this->shipping_method_id);
  932. if (isset($this->tracking_number)) $this->tracking_number=trim($this->tracking_number);
  933. if (isset($this->statut)) $this->statut=(int) $this->statut;
  934. if (isset($this->trueDepth)) $this->trueDepth=trim($this->trueDepth);
  935. if (isset($this->trueWidth)) $this->trueWidth=trim($this->trueWidth);
  936. if (isset($this->trueHeight)) $this->trueHeight=trim($this->trueHeight);
  937. if (isset($this->size_units)) $this->size_units=trim($this->size_units);
  938. if (isset($this->weight_units)) $this->weight_units=trim($this->weight_units);
  939. if (isset($this->trueWeight)) $this->weight=trim($this->trueWeight);
  940. if (isset($this->note_private)) $this->note=trim($this->note_private);
  941. if (isset($this->note_public)) $this->note=trim($this->note_public);
  942. if (isset($this->modelpdf)) $this->modelpdf=trim($this->modelpdf);
  943. // Check parameters
  944. // Put here code to add control on parameters values
  945. // Update request
  946. $sql = "UPDATE ".MAIN_DB_PREFIX."expedition SET";
  947. $sql.= " tms=".(dol_strlen($this->tms)!=0 ? "'".$this->db->idate($this->tms)."'" : 'null').",";
  948. $sql.= " ref=".(isset($this->ref)?"'".$this->db->escape($this->ref)."'":"null").",";
  949. $sql.= " ref_customer=".(isset($this->ref_customer)?"'".$this->db->escape($this->ref_customer)."'":"null").",";
  950. $sql.= " fk_soc=".(isset($this->socid)?$this->socid:"null").",";
  951. $sql.= " date_creation=".(dol_strlen($this->date_creation)!=0 ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
  952. $sql.= " fk_user_author=".(isset($this->fk_user_author)?$this->fk_user_author:"null").",";
  953. $sql.= " date_valid=".(dol_strlen($this->date_valid)!=0 ? "'".$this->db->idate($this->date_valid)."'" : 'null').",";
  954. $sql.= " fk_user_valid=".(isset($this->fk_user_valid)?$this->fk_user_valid:"null").",";
  955. $sql.= " date_expedition=".(dol_strlen($this->date_expedition)!=0 ? "'".$this->db->idate($this->date_expedition)."'" : 'null').",";
  956. $sql.= " date_delivery=".(dol_strlen($this->date_delivery)!=0 ? "'".$this->db->idate($this->date_delivery)."'" : 'null').",";
  957. $sql.= " fk_address=".(isset($this->fk_delivery_address)?$this->fk_delivery_address:"null").",";
  958. $sql.= " fk_shipping_method=".((isset($this->shipping_method_id) && $this->shipping_method_id > 0)?$this->shipping_method_id:"null").",";
  959. $sql.= " tracking_number=".(isset($this->tracking_number)?"'".$this->db->escape($this->tracking_number)."'":"null").",";
  960. $sql.= " fk_statut=".(isset($this->statut)?$this->statut:"null").",";
  961. $sql.= " fk_projet=".(isset($this->fk_project)?$this->fk_project:"null").",";
  962. $sql.= " height=".(($this->trueHeight != '')?$this->trueHeight:"null").",";
  963. $sql.= " width=".(($this->trueWidth != '')?$this->trueWidth:"null").",";
  964. $sql.= " size_units=".(isset($this->size_units)?$this->size_units:"null").",";
  965. $sql.= " size=".(($this->trueDepth != '')?$this->trueDepth:"null").",";
  966. $sql.= " weight_units=".(isset($this->weight_units)?$this->weight_units:"null").",";
  967. $sql.= " weight=".(($this->trueWeight != '')?$this->trueWeight:"null").",";
  968. $sql.= " note_private=".(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").",";
  969. $sql.= " note_public=".(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").",";
  970. $sql.= " model_pdf=".(isset($this->modelpdf)?"'".$this->db->escape($this->modelpdf)."'":"null").",";
  971. $sql.= " entity=".$conf->entity;
  972. $sql.= " WHERE rowid=".$this->id;
  973. $this->db->begin();
  974. dol_syslog(get_class($this)."::update", LOG_DEBUG);
  975. $resql = $this->db->query($sql);
  976. if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); }
  977. if (! $error)
  978. {
  979. if (! $notrigger)
  980. {
  981. // Call trigger
  982. $result=$this->call_trigger('SHIPPING_MODIFY', $user);
  983. if ($result < 0) { $error++; }
  984. // End call triggers
  985. }
  986. }
  987. // Commit or rollback
  988. if ($error)
  989. {
  990. foreach($this->errors as $errmsg)
  991. {
  992. dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
  993. $this->error.=($this->error?', '.$errmsg:$errmsg);
  994. }
  995. $this->db->rollback();
  996. return -1*$error;
  997. }
  998. else
  999. {
  1000. $this->db->commit();
  1001. return 1;
  1002. }
  1003. }
  1004. /**
  1005. * Delete shipment.
  1006. * Warning, do not delete a shipment if a delivery is linked to (with table llx_element_element)
  1007. *
  1008. * @param int $notrigger Disable triggers
  1009. * @param bool $also_update_stock true if the stock should be increased back (false by default)
  1010. * @return int >0 if OK, 0 if deletion done but failed to delete files, <0 if KO
  1011. */
  1012. public function delete($notrigger = 0, $also_update_stock = false)
  1013. {
  1014. global $conf, $langs, $user;
  1015. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  1016. require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
  1017. $error=0;
  1018. $this->error='';
  1019. $this->db->begin();
  1020. // Add a protection to refuse deleting if shipment has at least one delivery
  1021. $this->fetchObjectLinked($this->id, 'shipping', 0, 'delivery'); // Get deliveries linked to this shipment
  1022. if (count($this->linkedObjectsIds) > 0)
  1023. {
  1024. $this->error='ErrorThereIsSomeDeliveries';
  1025. $error++;
  1026. }
  1027. if (! $error)
  1028. {
  1029. if (! $notrigger)
  1030. {
  1031. // Call trigger
  1032. $result=$this->call_trigger('SHIPPING_DELETE', $user);
  1033. if ($result < 0) { $error++; }
  1034. // End call triggers
  1035. }
  1036. }
  1037. // Stock control
  1038. if (! $error && $conf->stock->enabled &&
  1039. (($conf->global->STOCK_CALCULATE_ON_SHIPMENT && $this->statut > self::STATUS_DRAFT) ||
  1040. ($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE && $this->statut == self::STATUS_CLOSED && $also_update_stock)))
  1041. {
  1042. require_once DOL_DOCUMENT_ROOT."/product/stock/class/mouvementstock.class.php";
  1043. $langs->load("agenda");
  1044. // Loop on each product line to add a stock movement
  1045. $sql = "SELECT cd.fk_product, cd.subprice, ed.qty, ed.fk_entrepot, ed.rowid as expeditiondet_id";
  1046. $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
  1047. $sql.= " ".MAIN_DB_PREFIX."expeditiondet as ed";
  1048. $sql.= " WHERE ed.fk_expedition = ".$this->id;
  1049. $sql.= " AND cd.rowid = ed.fk_origin_line";
  1050. dol_syslog(get_class($this)."::delete select details", LOG_DEBUG);
  1051. $resql=$this->db->query($sql);
  1052. if ($resql)
  1053. {
  1054. $cpt = $this->db->num_rows($resql);
  1055. for ($i = 0; $i < $cpt; $i++)
  1056. {
  1057. dol_syslog(get_class($this)."::delete movement index ".$i);
  1058. $obj = $this->db->fetch_object($resql);
  1059. $mouvS = new MouvementStock($this->db);
  1060. // we do not log origin because it will be deleted
  1061. $mouvS->origin = null;
  1062. // get lot/serial
  1063. $lotArray = null;
  1064. if ($conf->productbatch->enabled)
  1065. {
  1066. $lotArray = ExpeditionLineBatch::fetchAll($this->db, $obj->expeditiondet_id);
  1067. if (! is_array($lotArray))
  1068. {
  1069. $error++;$this->errors[]="Error ".$this->db->lasterror();
  1070. }
  1071. }
  1072. if (empty($lotArray)) {
  1073. // no lot/serial
  1074. // We increment stock of product (and sub-products)
  1075. // We use warehouse selected for each line
  1076. $result=$mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $obj->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref)); // Price is set to 0, because we don't want to see WAP changed
  1077. if ($result < 0)
  1078. {
  1079. $error++;$this->errors=$this->errors + $mouvS->errors;
  1080. break;
  1081. }
  1082. }
  1083. else
  1084. {
  1085. // We increment stock of batches
  1086. // We use warehouse selected for each line
  1087. foreach($lotArray as $lot)
  1088. {
  1089. $result=$mouvS->reception($user, $obj->fk_product, $obj->fk_entrepot, $lot->qty, 0, $langs->trans("ShipmentDeletedInDolibarr", $this->ref), $lot->eatby, $lot->sellby, $lot->batch); // Price is set to 0, because we don't want to see WAP changed
  1090. if ($result < 0)
  1091. {
  1092. $error++;$this->errors=$this->errors + $mouvS->errors;
  1093. break;
  1094. }
  1095. }
  1096. if ($error) break; // break for loop incase of error
  1097. }
  1098. }
  1099. }
  1100. else
  1101. {
  1102. $error++;$this->errors[]="Error ".$this->db->lasterror();
  1103. }
  1104. }
  1105. // delete batch expedition line
  1106. if (! $error && $conf->productbatch->enabled)
  1107. {
  1108. if (ExpeditionLineBatch::deletefromexp($this->db, $this->id) < 0)
  1109. {
  1110. $error++;$this->errors[]="Error ".$this->db->lasterror();
  1111. }
  1112. }
  1113. if (! $error)
  1114. {
  1115. $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
  1116. $sql.= " WHERE fk_expedition = ".$this->id;
  1117. if ( $this->db->query($sql) )
  1118. {
  1119. // Delete linked object
  1120. $res = $this->deleteObjectLinked();
  1121. if ($res < 0) $error++;
  1122. if (! $error)
  1123. {
  1124. $sql = "DELETE FROM ".MAIN_DB_PREFIX."expedition";
  1125. $sql.= " WHERE rowid = ".$this->id;
  1126. if ($this->db->query($sql))
  1127. {
  1128. if (! empty($this->origin) && $this->origin_id > 0)
  1129. {
  1130. $this->fetch_origin();
  1131. $origin=$this->origin;
  1132. if ($this->$origin->statut == Commande::STATUS_SHIPMENTONPROCESS) // If order source of shipment is "shipment in progress"
  1133. {
  1134. // Check if there is no more shipment. If not, we can move back status of order to "validated" instead of "shipment in progress"
  1135. $this->$origin->loadExpeditions();
  1136. //var_dump($this->$origin->expeditions);exit;
  1137. if (count($this->$origin->expeditions) <= 0)
  1138. {
  1139. $this->$origin->setStatut(Commande::STATUS_VALIDATED);
  1140. }
  1141. }
  1142. }
  1143. if (! $error)
  1144. {
  1145. $this->db->commit();
  1146. // We delete PDFs
  1147. $ref = dol_sanitizeFileName($this->ref);
  1148. if (! empty($conf->expedition->dir_output))
  1149. {
  1150. $dir = $conf->expedition->dir_output . '/sending/' . $ref ;
  1151. $file = $dir . '/' . $ref . '.pdf';
  1152. if (file_exists($file))
  1153. {
  1154. if (! dol_delete_file($file))
  1155. {
  1156. return 0;
  1157. }
  1158. }
  1159. if (file_exists($dir))
  1160. {
  1161. if (!dol_delete_dir_recursive($dir))
  1162. {
  1163. $this->error=$langs->trans("ErrorCanNotDeleteDir", $dir);
  1164. return 0;
  1165. }
  1166. }
  1167. }
  1168. return 1;
  1169. }
  1170. else
  1171. {
  1172. $this->db->rollback();
  1173. return -1;
  1174. }
  1175. }
  1176. else
  1177. {
  1178. $this->error=$this->db->lasterror()." - sql=$sql";
  1179. $this->db->rollback();
  1180. return -3;
  1181. }
  1182. }
  1183. else
  1184. {
  1185. $this->error=$this->db->lasterror()." - sql=$sql";
  1186. $this->db->rollback();
  1187. return -2;
  1188. }
  1189. }
  1190. else
  1191. {
  1192. $this->error=$this->db->lasterror()." - sql=$sql";
  1193. $this->db->rollback();
  1194. return -1;
  1195. }
  1196. }
  1197. else
  1198. {
  1199. $this->db->rollback();
  1200. return -1;
  1201. }
  1202. }
  1203. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1204. /**
  1205. * Load lines
  1206. *
  1207. * @return int >0 if OK, Otherwise if KO
  1208. */
  1209. public function fetch_lines()
  1210. {
  1211. // phpcs:enable
  1212. global $conf, $mysoc;
  1213. // TODO: recuperer les champs du document associe a part
  1214. $this->lines=array();
  1215. $sql = "SELECT cd.rowid, cd.fk_product, cd.label as custom_label, cd.description, cd.qty as qty_asked, cd.product_type";
  1216. $sql.= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva";
  1217. $sql.= ", cd.vat_src_code, cd.tva_tx, cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.info_bits, cd.price, cd.subprice, cd.remise_percent,cd.buy_price_ht as pa_ht";
  1218. $sql.= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc";
  1219. $sql.= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot";
  1220. $sql.= ", p.ref as product_ref, p.label as product_label, p.fk_product_type";
  1221. $sql.= ", p.weight, p.weight_units, p.length, p.length_units, p.surface, p.surface_units, p.volume, p.volume_units, p.tobatch as product_tobatch";
  1222. $sql.= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
  1223. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
  1224. $sql.= " WHERE ed.fk_expedition = ".$this->id;
  1225. $sql.= " AND ed.fk_origin_line = cd.rowid";
  1226. $sql.= " ORDER BY cd.rang, ed.fk_origin_line";
  1227. dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
  1228. $resql = $this->db->query($sql);
  1229. if ($resql)
  1230. {
  1231. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  1232. $num = $this->db->num_rows($resql);
  1233. $i = 0;
  1234. $lineindex = 0;
  1235. $originline = 0;
  1236. $this->total_ht = 0;
  1237. $this->total_tva = 0;
  1238. $this->total_ttc = 0;
  1239. $this->total_localtax1 = 0;
  1240. $this->total_localtax2 = 0;
  1241. $line = new ExpeditionLigne($this->db);
  1242. while ($i < $num)
  1243. {
  1244. $obj = $this->db->fetch_object($resql);
  1245. if ($originline == $obj->fk_origin_line) {
  1246. $line->entrepot_id = 0; // entrepod_id in details_entrepot
  1247. $line->qty_shipped += $obj->qty_shipped;
  1248. } else {
  1249. $line = new ExpeditionLigne($this->db);
  1250. $line->entrepot_id = $obj->fk_entrepot;
  1251. $line->qty_shipped = $obj->qty_shipped;
  1252. }
  1253. $detail_entrepot = new stdClass;
  1254. $detail_entrepot->entrepot_id = $obj->fk_entrepot;
  1255. $detail_entrepot->qty_shipped = $obj->qty_shipped;
  1256. $detail_entrepot->line_id = $obj->line_id;
  1257. $line->details_entrepot[] = $detail_entrepot;
  1258. $line->line_id = $obj->line_id;
  1259. $line->rowid = $obj->line_id; // TODO deprecated
  1260. $line->id = $obj->line_id;
  1261. $line->fk_origin = 'orderline';
  1262. $line->fk_origin_line = $obj->fk_origin_line;
  1263. $line->origin_line_id = $obj->fk_origin_line; // TODO deprecated
  1264. $line->fk_expedition = $this->id; // id of parent
  1265. $line->product_type = $obj->product_type;
  1266. $line->fk_product = $obj->fk_product;
  1267. $line->fk_product_type = $obj->fk_product_type;
  1268. $line->ref = $obj->product_ref; // TODO deprecated
  1269. $line->product_ref = $obj->product_ref;
  1270. $line->product_label = $obj->product_label;
  1271. $line->libelle = $obj->product_label; // TODO deprecated
  1272. $line->product_tobatch = $obj->product_tobatch;
  1273. $line->label = $obj->custom_label;
  1274. $line->description = $obj->description;
  1275. $line->qty_asked = $obj->qty_asked;
  1276. $line->weight = $obj->weight;
  1277. $line->weight_units = $obj->weight_units;
  1278. $line->length = $obj->length;
  1279. $line->length_units = $obj->length_units;
  1280. $line->surface = $obj->surface;
  1281. $line->surface_units = $obj->surface_units;
  1282. $line->volume = $obj->volume;
  1283. $line->volume_units = $obj->volume_units;
  1284. $line->pa_ht = $obj->pa_ht;
  1285. // Local taxes
  1286. $localtax_array=array(0=>$obj->localtax1_type, 1=>$obj->localtax1_tx, 2=>$obj->localtax2_type, 3=>$obj->localtax2_tx);
  1287. $localtax1_tx = get_localtax($obj->tva_tx, 1, $this->thirdparty);
  1288. $localtax2_tx = get_localtax($obj->tva_tx, 2, $this->thirdparty);
  1289. // For invoicing
  1290. $tabprice = calcul_price_total($obj->qty_shipped, $obj->subprice, $obj->remise_percent, $obj->tva_tx, $localtax1_tx, $localtax2_tx, 0, 'HT', $obj->info_bits, $obj->fk_product_type, $mysoc, $localtax_array); // We force type to 0
  1291. $line->desc = $obj->description; // We need ->desc because some code into CommonObject use desc (property defined for other elements)
  1292. $line->qty = $line->qty_shipped;
  1293. $line->total_ht = $tabprice[0];
  1294. $line->total_localtax1 = $tabprice[9];
  1295. $line->total_localtax2 = $tabprice[10];
  1296. $line->total_ttc = $tabprice[2];
  1297. $line->total_tva = $tabprice[1];
  1298. $line->vat_src_code = $obj->vat_src_code;
  1299. $line->tva_tx = $obj->tva_tx;
  1300. $line->localtax1_tx = $obj->localtax1_tx;
  1301. $line->localtax2_tx = $obj->localtax2_tx;
  1302. $line->info_bits = $obj->info_bits;
  1303. $line->price = $obj->price;
  1304. $line->subprice = $obj->subprice;
  1305. $line->remise_percent = $obj->remise_percent;
  1306. $this->total_ht+= $tabprice[0];
  1307. $this->total_tva+= $tabprice[1];
  1308. $this->total_ttc+= $tabprice[2];
  1309. $this->total_localtax1+= $tabprice[9];
  1310. $this->total_localtax2+= $tabprice[10];
  1311. // Multicurrency
  1312. $this->fk_multicurrency = $obj->fk_multicurrency;
  1313. $this->multicurrency_code = $obj->multicurrency_code;
  1314. $this->multicurrency_subprice = $obj->multicurrency_subprice;
  1315. $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
  1316. $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
  1317. $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
  1318. if ($originline != $obj->fk_origin_line)
  1319. {
  1320. $line->detail_batch = array();
  1321. }
  1322. // Detail of batch
  1323. if (! empty($conf->productbatch->enabled) && $obj->line_id > 0 && $obj->product_tobatch > 0)
  1324. {
  1325. require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
  1326. $newdetailbatch = ExpeditionLineBatch::fetchAll($this->db, $obj->line_id, $obj->fk_product);
  1327. if (is_array($newdetailbatch))
  1328. {
  1329. if ($originline != $obj->fk_origin_line)
  1330. {
  1331. $line->detail_batch = $newdetailbatch;
  1332. }
  1333. else
  1334. {
  1335. $line->detail_batch = array_merge($line->detail_batch, $newdetailbatch);
  1336. }
  1337. }
  1338. }
  1339. if ($originline != $obj->fk_origin_line)
  1340. {
  1341. $this->lines[$lineindex] = $line;
  1342. $lineindex++;
  1343. }
  1344. else
  1345. {
  1346. $line->total_ht += $tabprice[0];
  1347. $line->total_localtax1 += $tabprice[9];
  1348. $line->total_localtax2 += $tabprice[10];
  1349. $line->total_ttc += $tabprice[2];
  1350. $line->total_tva += $tabprice[1];
  1351. }
  1352. $i++;
  1353. $originline = $obj->fk_origin_line;
  1354. }
  1355. $this->db->free($resql);
  1356. return 1;
  1357. }
  1358. else
  1359. {
  1360. $this->error=$this->db->error();
  1361. return -3;
  1362. }
  1363. }
  1364. /**
  1365. * Delete detail line
  1366. *
  1367. * @param User $user User making deletion
  1368. * @param int $lineid Id of line to delete
  1369. * @return int >0 if OK, <0 if KO
  1370. */
  1371. public function deleteline($user, $lineid)
  1372. {
  1373. global $user;
  1374. if ($this->statut == self::STATUS_DRAFT)
  1375. {
  1376. $this->db->begin();
  1377. $line=new ExpeditionLigne($this->db);
  1378. // For triggers
  1379. $line->fetch($lineid);
  1380. if ($line->delete($user) > 0)
  1381. {
  1382. //$this->update_price(1);
  1383. $this->db->commit();
  1384. return 1;
  1385. }
  1386. else
  1387. {
  1388. $this->db->rollback();
  1389. return -1;
  1390. }
  1391. }
  1392. else
  1393. {
  1394. $this->error='ErrorDeleteLineNotAllowedByObjectStatus';
  1395. return -2;
  1396. }
  1397. }
  1398. /**
  1399. * Return clicable link of object (with eventually picto)
  1400. *
  1401. * @param int $withpicto Add picto into link
  1402. * @param string $option Where the link point to
  1403. * @param int $max Max length to show
  1404. * @param int $short Use short labels
  1405. * @param int $notooltip 1=No tooltip
  1406. * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
  1407. * @return string String with URL
  1408. */
  1409. public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
  1410. {
  1411. global $langs;
  1412. $result='';
  1413. $label = '<u>' . $langs->trans("ShowSending") . '</u>';
  1414. $label .= '<br><b>' . $langs->trans('Ref') . ':</b> '.$this->ref;
  1415. $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
  1416. $url = DOL_URL_ROOT.'/expedition/card.php?id='.$this->id;
  1417. if ($short) return $url;
  1418. if ($option !== 'nolink')
  1419. {
  1420. // Add param to save lastsearch_values or not
  1421. $add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
  1422. if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
  1423. if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
  1424. }
  1425. $linkclose='';
  1426. if (empty($notooltip))
  1427. {
  1428. if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
  1429. {
  1430. $label=$langs->trans("ShowSending");
  1431. $linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
  1432. }
  1433. $linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"';
  1434. $linkclose.=' class="classfortooltip"';
  1435. }
  1436. $linkstart = '<a href="'.$url.'" title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip">';
  1437. $linkend='</a>';
  1438. $result .= $linkstart;
  1439. if ($withpicto) $result.=img_object(($notooltip?'':$label), $this->picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1);
  1440. if ($withpicto != 2) $result.= $this->ref;
  1441. $result .= $linkend;
  1442. return $result;
  1443. }
  1444. /**
  1445. * Return status label
  1446. *
  1447. * @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto
  1448. * @return string Libelle
  1449. */
  1450. public function getLibStatut($mode = 0)
  1451. {
  1452. return $this->LibStatut($this->statut, $mode);
  1453. }
  1454. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1455. /**
  1456. * Return label of a status
  1457. *
  1458. * @param int $statut Id statut
  1459. * @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto
  1460. * @return string Label of status
  1461. */
  1462. public function LibStatut($statut, $mode)
  1463. {
  1464. // phpcs:enable
  1465. global $langs;
  1466. if ($mode==0)
  1467. {
  1468. if ($statut==0) return $langs->trans($this->statuts[$statut]);
  1469. elseif ($statut==1) return $langs->trans($this->statuts[$statut]);
  1470. elseif ($statut==2) return $langs->trans($this->statuts[$statut]);
  1471. }
  1472. elseif ($mode==1)
  1473. {
  1474. if ($statut==0) return $langs->trans($this->statutshorts[$statut]);
  1475. elseif ($statut==1) return $langs->trans($this->statutshorts[$statut]);
  1476. elseif ($statut==2) return $langs->trans($this->statutshorts[$statut]);
  1477. }
  1478. elseif ($mode == 3)
  1479. {
  1480. if ($statut==0) return img_picto($langs->trans($this->statuts[$statut]), 'statut0');
  1481. elseif ($statut==1) return img_picto($langs->trans($this->statuts[$statut]), 'statut4');
  1482. elseif ($statut==2) return img_picto($langs->trans($this->statuts[$statut]), 'statut6');
  1483. }
  1484. elseif ($mode == 4)
  1485. {
  1486. if ($statut==0) return img_picto($langs->trans($this->statuts[$statut]), 'statut0').' '.$langs->trans($this->statuts[$statut]);
  1487. elseif ($statut==1) return img_picto($langs->trans($this->statuts[$statut]), 'statut4').' '.$langs->trans($this->statuts[$statut]);
  1488. elseif ($statut==2) return img_picto($langs->trans($this->statuts[$statut]), 'statut6').' '.$langs->trans($this->statuts[$statut]);
  1489. }
  1490. elseif ($mode == 5)
  1491. {
  1492. if ($statut==0) return $langs->trans($this->statutshorts[$statut]).' '.img_picto($langs->trans($this->statuts[$statut]), 'statut0');
  1493. elseif ($statut==1) return $langs->trans($this->statutshorts[$statut]).' '.img_picto($langs->trans($this->statuts[$statut]), 'statut4');
  1494. elseif ($statut==2) return $langs->trans($this->statutshorts[$statut]).' '.img_picto($langs->trans($this->statuts[$statut]), 'statut6');
  1495. }
  1496. }
  1497. /**
  1498. * Initialise an instance with random values.
  1499. * Used to build previews or test instances.
  1500. * id must be 0 if object instance is a specimen.
  1501. *
  1502. * @return void
  1503. */
  1504. public function initAsSpecimen()
  1505. {
  1506. global $langs;
  1507. $now=dol_now();
  1508. dol_syslog(get_class($this)."::initAsSpecimen");
  1509. // Load array of products prodids
  1510. $num_prods = 0;
  1511. $prodids = array();
  1512. $sql = "SELECT rowid";
  1513. $sql.= " FROM ".MAIN_DB_PREFIX."product";
  1514. $sql.= " WHERE entity IN (".getEntity('product').")";
  1515. $resql = $this->db->query($sql);
  1516. if ($resql)
  1517. {
  1518. $num_prods = $this->db->num_rows($resql);
  1519. $i = 0;
  1520. while ($i < $num_prods)
  1521. {
  1522. $i++;
  1523. $row = $this->db->fetch_row($resql);
  1524. $prodids[$i] = $row[0];
  1525. }
  1526. }
  1527. $order=new Commande($this->db);
  1528. $order->initAsSpecimen();
  1529. // Initialise parametres
  1530. $this->id=0;
  1531. $this->ref = 'SPECIMEN';
  1532. $this->specimen=1;
  1533. $this->statut = self::STATUS_VALIDATED;
  1534. $this->livraison_id = 0;
  1535. $this->date = $now;
  1536. $this->date_creation = $now;
  1537. $this->date_valid = $now;
  1538. $this->date_delivery = $now;
  1539. $this->date_expedition = $now + 24*3600;
  1540. $this->entrepot_id = 0;
  1541. $this->fk_delivery_address = 0;
  1542. $this->socid = 1;
  1543. $this->commande_id = 0;
  1544. $this->commande = $order;
  1545. $this->origin_id = 1;
  1546. $this->origin = 'commande';
  1547. $this->note_private = 'Private note';
  1548. $this->note_public = 'Public note';
  1549. $nbp = 5;
  1550. $xnbp = 0;
  1551. while ($xnbp < $nbp)
  1552. {
  1553. $line=new ExpeditionLigne($this->db);
  1554. $line->desc=$langs->trans("Description")." ".$xnbp;
  1555. $line->libelle=$langs->trans("Description")." ".$xnbp;
  1556. $line->qty=10;
  1557. $line->qty_asked=5;
  1558. $line->qty_shipped=4;
  1559. $line->fk_product=$this->commande->lines[$xnbp]->fk_product;
  1560. $this->lines[]=$line;
  1561. $xnbp++;
  1562. }
  1563. }
  1564. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1565. /**
  1566. * Set the planned delivery date
  1567. *
  1568. * @param User $user Objet user that modify
  1569. * @param integer $date_livraison Date of delivery
  1570. * @return int <0 if KO, >0 if OK
  1571. */
  1572. public function set_date_livraison($user, $date_livraison)
  1573. {
  1574. // phpcs:enable
  1575. if ($user->rights->expedition->creer)
  1576. {
  1577. $sql = "UPDATE ".MAIN_DB_PREFIX."expedition";
  1578. $sql.= " SET date_delivery = ".($date_livraison ? "'".$this->db->idate($date_livraison)."'" : 'null');
  1579. $sql.= " WHERE rowid = ".$this->id;
  1580. dol_syslog(get_class($this)."::set_date_livraison", LOG_DEBUG);
  1581. $resql=$this->db->query($sql);
  1582. if ($resql)
  1583. {
  1584. $this->date_delivery = $date_livraison;
  1585. return 1;
  1586. }
  1587. else
  1588. {
  1589. $this->error=$this->db->error();
  1590. return -1;
  1591. }
  1592. }
  1593. else
  1594. {
  1595. return -2;
  1596. }
  1597. }
  1598. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1599. /**
  1600. * Fetch deliveries method and return an array. Load array this->meths(rowid=>label).
  1601. *
  1602. * @return void
  1603. */
  1604. public function fetch_delivery_methods()
  1605. {
  1606. // phpcs:enable
  1607. global $langs;
  1608. $this->meths = array();
  1609. $sql = "SELECT em.rowid, em.code, em.libelle";
  1610. $sql.= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
  1611. $sql.= " WHERE em.active = 1";
  1612. $sql.= " ORDER BY em.libelle ASC";
  1613. $resql = $this->db->query($sql);
  1614. if ($resql)
  1615. {
  1616. while ($obj = $this->db->fetch_object($resql))
  1617. {
  1618. $label=$langs->trans('SendingMethod'.$obj->code);
  1619. $this->meths[$obj->rowid] = ($label != 'SendingMethod'.$obj->code?$label:$obj->libelle);
  1620. }
  1621. }
  1622. }
  1623. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1624. /**
  1625. * Fetch all deliveries method and return an array. Load array this->listmeths.
  1626. *
  1627. * @param int $id only this carrier, all if none
  1628. * @return void
  1629. */
  1630. public function list_delivery_methods($id = '')
  1631. {
  1632. // phpcs:enable
  1633. global $langs;
  1634. $this->listmeths = array();
  1635. $i=0;
  1636. $sql = "SELECT em.rowid, em.code, em.libelle, em.description, em.tracking, em.active";
  1637. $sql.= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
  1638. if ($id!='') $sql.= " WHERE em.rowid=".$id;
  1639. $resql = $this->db->query($sql);
  1640. if ($resql)
  1641. {
  1642. while ($obj = $this->db->fetch_object($resql))
  1643. {
  1644. $this->listmeths[$i]['rowid'] = $obj->rowid;
  1645. $this->listmeths[$i]['code'] = $obj->code;
  1646. $label=$langs->trans('SendingMethod'.$obj->code);
  1647. $this->listmeths[$i]['libelle'] = ($label != 'SendingMethod'.$obj->code?$label:$obj->libelle);
  1648. $this->listmeths[$i]['description'] = $obj->description;
  1649. $this->listmeths[$i]['tracking'] = $obj->tracking;
  1650. $this->listmeths[$i]['active'] = $obj->active;
  1651. $i++;
  1652. }
  1653. }
  1654. }
  1655. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1656. /**
  1657. * Update/create delivery method.
  1658. *
  1659. * @param string $id id method to activate
  1660. *
  1661. * @return void
  1662. */
  1663. public function update_delivery_method($id = '')
  1664. {
  1665. // phpcs:enable
  1666. if ($id=='')
  1667. {
  1668. $sql = "INSERT INTO ".MAIN_DB_PREFIX."c_shipment_mode (code, libelle, description, tracking)";
  1669. $sql.=" VALUES ('".$this->db->escape($this->update['code'])."','".$this->db->escape($this->update['libelle'])."','".$this->db->escape($this->update['description'])."','".$this->db->escape($this->update['tracking'])."')";
  1670. $resql = $this->db->query($sql);
  1671. }
  1672. else
  1673. {
  1674. $sql = "UPDATE ".MAIN_DB_PREFIX."c_shipment_mode SET";
  1675. $sql.= " code='".$this->db->escape($this->update['code'])."'";
  1676. $sql.= ",libelle='".$this->db->escape($this->update['libelle'])."'";
  1677. $sql.= ",description='".$this->db->escape($this->update['description'])."'";
  1678. $sql.= ",tracking='".$this->db->escape($this->update['tracking'])."'";
  1679. $sql.= " WHERE rowid=".$id;
  1680. $resql = $this->db->query($sql);
  1681. }
  1682. if ($resql < 0) dol_print_error($this->db, '');
  1683. }
  1684. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1685. /**
  1686. * Activate delivery method.
  1687. *
  1688. * @param int $id id method to activate
  1689. * @return void
  1690. */
  1691. public function activ_delivery_method($id)
  1692. {
  1693. // phpcs:enable
  1694. $sql = 'UPDATE '.MAIN_DB_PREFIX.'c_shipment_mode SET active=1';
  1695. $sql.= ' WHERE rowid='.$id;
  1696. $resql = $this->db->query($sql);
  1697. }
  1698. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1699. /**
  1700. * DesActivate delivery method.
  1701. *
  1702. * @param int $id id method to desactivate
  1703. *
  1704. * @return void
  1705. */
  1706. public function disable_delivery_method($id)
  1707. {
  1708. // phpcs:enable
  1709. $sql = 'UPDATE '.MAIN_DB_PREFIX.'c_shipment_mode SET active=0';
  1710. $sql.= ' WHERE rowid='.$id;
  1711. $resql = $this->db->query($sql);
  1712. }
  1713. /**
  1714. * Forge an set tracking url
  1715. *
  1716. * @param string $value Value
  1717. * @return void
  1718. */
  1719. public function getUrlTrackingStatus($value = '')
  1720. {
  1721. if (! empty($this->shipping_method_id))
  1722. {
  1723. $sql = "SELECT em.code, em.tracking";
  1724. $sql.= " FROM ".MAIN_DB_PREFIX."c_shipment_mode as em";
  1725. $sql.= " WHERE em.rowid = ".$this->shipping_method_id;
  1726. $resql = $this->db->query($sql);
  1727. if ($resql)
  1728. {
  1729. if ($obj = $this->db->fetch_object($resql))
  1730. {
  1731. $tracking = $obj->tracking;
  1732. }
  1733. }
  1734. }
  1735. if (!empty($tracking) && !empty($value))
  1736. {
  1737. $url = str_replace('{TRACKID}', $value, $tracking);
  1738. $this->tracking_url = sprintf('<a target="_blank" href="%s">'.($value?$value:'url').'</a>', $url, $url);
  1739. }
  1740. else
  1741. {
  1742. $this->tracking_url = $value;
  1743. }
  1744. }
  1745. /**
  1746. * Classify the shipping as closed.
  1747. *
  1748. * @return int <0 if KO, >0 if OK
  1749. */
  1750. public function setClosed()
  1751. {
  1752. global $conf,$langs,$user;
  1753. $error=0;
  1754. // Protection. This avoid to move stock later when we should not
  1755. if ($this->statut == self::STATUS_CLOSED)
  1756. {
  1757. return 0;
  1758. }
  1759. $this->db->begin();
  1760. $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut='.self::STATUS_CLOSED;
  1761. $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > 0';
  1762. $resql=$this->db->query($sql);
  1763. if ($resql)
  1764. {
  1765. // Set order billed if 100% of order is shipped (qty in shipment lines match qty in order lines)
  1766. if ($this->origin == 'commande' && $this->origin_id > 0)
  1767. {
  1768. $order = new Commande($this->db);
  1769. $order->fetch($this->origin_id);
  1770. $order->loadExpeditions(self::STATUS_CLOSED); // Fill $order->expeditions = array(orderlineid => qty)
  1771. $shipments_match_order = 1;
  1772. foreach($order->lines as $line)
  1773. {
  1774. $lineid = $line->id;
  1775. $qty = $line->qty;
  1776. if (($line->product_type == 0 || ! empty($conf->global->STOCK_SUPPORTS_SERVICES)) && $order->expeditions[$lineid] != $qty)
  1777. {
  1778. $shipments_match_order = 0;
  1779. $text='Qty for order line id '.$lineid.' is '.$qty.'. However in the shipments with status Expedition::STATUS_CLOSED='.self::STATUS_CLOSED.' we have qty = '.$order->expeditions[$lineid].', so we can t close order';
  1780. dol_syslog($text);
  1781. break;
  1782. }
  1783. }
  1784. if ($shipments_match_order)
  1785. {
  1786. dol_syslog("Qty for the ".count($order->lines)." lines of order have same value for shipments with status Expedition::STATUS_CLOSED=".self::STATUS_CLOSED.', so we close order');
  1787. $order->cloture($user);
  1788. }
  1789. }
  1790. $this->statut=self::STATUS_CLOSED;
  1791. // If stock increment is done on closing
  1792. if (! $error && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE))
  1793. {
  1794. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
  1795. $langs->load("agenda");
  1796. // Loop on each product line to add a stock movement
  1797. // TODO possibilite d'expedier a partir d'une propale ou autre origine ?
  1798. $sql = "SELECT cd.fk_product, cd.subprice,";
  1799. $sql.= " ed.rowid, ed.qty, ed.fk_entrepot,";
  1800. $sql.= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
  1801. $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
  1802. $sql.= " ".MAIN_DB_PREFIX."expeditiondet as ed";
  1803. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
  1804. $sql.= " WHERE ed.fk_expedition = ".$this->id;
  1805. $sql.= " AND cd.rowid = ed.fk_origin_line";
  1806. dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
  1807. $resql=$this->db->query($sql);
  1808. if ($resql)
  1809. {
  1810. $cpt = $this->db->num_rows($resql);
  1811. for ($i = 0; $i < $cpt; $i++)
  1812. {
  1813. $obj = $this->db->fetch_object($resql);
  1814. if (empty($obj->edbrowid))
  1815. {
  1816. $qty = $obj->qty;
  1817. }
  1818. else
  1819. {
  1820. $qty = $obj->edbqty;
  1821. }
  1822. if ($qty <= 0) continue;
  1823. dol_syslog(get_class($this)."::valid movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
  1824. $mouvS = new MouvementStock($this->db);
  1825. $mouvS->origin = &$this;
  1826. if (empty($obj->edbrowid))
  1827. {
  1828. // line without batch detail
  1829. // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
  1830. $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentClassifyClosedInDolibarr", $numref));
  1831. if ($result < 0) {
  1832. $this->error = $mouvS->error;
  1833. $this->errors = $mouvS->errors;
  1834. $error++; break;
  1835. }
  1836. }
  1837. else
  1838. {
  1839. // line with batch detail
  1840. // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
  1841. $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentClassifyClosedInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
  1842. if ($result < 0) {
  1843. $this->error = $mouvS->error;
  1844. $this->errors = $mouvS->errors;
  1845. $error++; break;
  1846. }
  1847. }
  1848. }
  1849. }
  1850. else
  1851. {
  1852. $this->error=$this->db->lasterror();
  1853. $error++;
  1854. }
  1855. }
  1856. // Call trigger
  1857. if (! $error)
  1858. {
  1859. $result=$this->call_trigger('SHIPPING_CLOSED', $user);
  1860. if ($result < 0) {
  1861. $error++;
  1862. }
  1863. }
  1864. }
  1865. else
  1866. {
  1867. dol_print_error($this->db);
  1868. $error++;
  1869. }
  1870. if (! $error)
  1871. {
  1872. $this->db->commit();
  1873. return 1;
  1874. }
  1875. else
  1876. {
  1877. $this->statut = self::STATUS_VALIDATED;
  1878. $this->db->rollback();
  1879. return -1;
  1880. }
  1881. }
  1882. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1883. /**
  1884. * Classify the shipping as invoiced (used when WORKFLOW_BILL_ON_SHIPMENT is on)
  1885. *
  1886. * @return int <0 if ko, >0 if ok
  1887. */
  1888. public function set_billed()
  1889. {
  1890. // phpcs:enable
  1891. global $user;
  1892. $error=0;
  1893. $this->db->begin();
  1894. $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=2, billed=1'; // TODO Update only billed
  1895. $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > 0';
  1896. $resql=$this->db->query($sql);
  1897. if ($resql)
  1898. {
  1899. $this->statut=self::STATUS_CLOSED;
  1900. $this->billed=1;
  1901. // Call trigger
  1902. $result=$this->call_trigger('SHIPPING_BILLED', $user);
  1903. if ($result < 0) {
  1904. $error++;
  1905. }
  1906. } else {
  1907. $error++;
  1908. $this->errors[]=$this->db->lasterror;
  1909. }
  1910. if (empty($error)) {
  1911. $this->db->commit();
  1912. return 1;
  1913. }
  1914. else
  1915. {
  1916. $this->statut=self::STATUS_VALIDATED;
  1917. $this->billed=0;
  1918. $this->db->rollback();
  1919. return -1;
  1920. }
  1921. }
  1922. /**
  1923. * Classify the shipping as validated/opened
  1924. *
  1925. * @return int <0 if KO, 0 if already open, >0 if OK
  1926. */
  1927. public function reOpen()
  1928. {
  1929. global $conf,$langs,$user;
  1930. $error=0;
  1931. // Protection. This avoid to move stock later when we should not
  1932. if ($this->statut == self::STATUS_VALIDATED)
  1933. {
  1934. return 0;
  1935. }
  1936. $this->db->begin();
  1937. $oldbilled=$this->billed;
  1938. $sql = 'UPDATE '.MAIN_DB_PREFIX.'expedition SET fk_statut=1';
  1939. $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > 0';
  1940. $resql=$this->db->query($sql);
  1941. if ($resql)
  1942. {
  1943. $this->statut=self::STATUS_VALIDATED;
  1944. $this->billed=0;
  1945. // If stock increment is done on closing
  1946. if (! $error && ! empty($conf->stock->enabled) && ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE))
  1947. {
  1948. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
  1949. $langs->load("agenda");
  1950. // Loop on each product line to add a stock movement
  1951. // TODO possibilite d'expedier a partir d'une propale ou autre origine
  1952. $sql = "SELECT cd.fk_product, cd.subprice,";
  1953. $sql.= " ed.rowid, ed.qty, ed.fk_entrepot,";
  1954. $sql.= " edb.rowid as edbrowid, edb.eatby, edb.sellby, edb.batch, edb.qty as edbqty, edb.fk_origin_stock";
  1955. $sql.= " FROM ".MAIN_DB_PREFIX."commandedet as cd,";
  1956. $sql.= " ".MAIN_DB_PREFIX."expeditiondet as ed";
  1957. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."expeditiondet_batch as edb on edb.fk_expeditiondet = ed.rowid";
  1958. $sql.= " WHERE ed.fk_expedition = ".$this->id;
  1959. $sql.= " AND cd.rowid = ed.fk_origin_line";
  1960. dol_syslog(get_class($this)."::valid select details", LOG_DEBUG);
  1961. $resql=$this->db->query($sql);
  1962. if ($resql)
  1963. {
  1964. $cpt = $this->db->num_rows($resql);
  1965. for ($i = 0; $i < $cpt; $i++)
  1966. {
  1967. $obj = $this->db->fetch_object($resql);
  1968. if (empty($obj->edbrowid))
  1969. {
  1970. $qty = $obj->qty;
  1971. }
  1972. else
  1973. {
  1974. $qty = $obj->edbqty;
  1975. }
  1976. if ($qty <= 0) continue;
  1977. dol_syslog(get_class($this)."::reopen expedition movement index ".$i." ed.rowid=".$obj->rowid." edb.rowid=".$obj->edbrowid);
  1978. //var_dump($this->lines[$i]);
  1979. $mouvS = new MouvementStock($this->db);
  1980. $mouvS->origin = &$this;
  1981. if (empty($obj->edbrowid))
  1982. {
  1983. // line without batch detail
  1984. // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
  1985. $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $numref));
  1986. if ($result < 0) {
  1987. $this->error = $mouvS->error;
  1988. $this->errors = $mouvS->errors;
  1989. $error++; break;
  1990. }
  1991. }
  1992. else
  1993. {
  1994. // line with batch detail
  1995. // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record
  1996. $result=$mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, -$qty, $obj->subprice, $langs->trans("ShipmentUnClassifyCloseddInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock);
  1997. if ($result < 0) {
  1998. $this->error = $mouvS->error;
  1999. $this->errors = $mouvS->errors;
  2000. $error++; break;
  2001. }
  2002. }
  2003. }
  2004. }
  2005. else
  2006. {
  2007. $this->error=$this->db->lasterror();
  2008. $error++;
  2009. }
  2010. }
  2011. if (! $error) {
  2012. // Call trigger
  2013. $result=$this->call_trigger('SHIPPING_REOPEN', $user);
  2014. if ($result < 0) {
  2015. $error++;
  2016. }
  2017. }
  2018. } else {
  2019. $error++;
  2020. $this->errors[]=$this->db->lasterror();
  2021. }
  2022. if (! $error)
  2023. {
  2024. $this->db->commit();
  2025. return 1;
  2026. }
  2027. else
  2028. {
  2029. $this->statut=self::STATUS_CLOSED;
  2030. $this->billed=$oldbilled;
  2031. $this->db->rollback();
  2032. return -1;
  2033. }
  2034. }
  2035. /**
  2036. * Create a document onto disk according to template module.
  2037. *
  2038. * @param string $modele Force the model to using ('' to not force)
  2039. * @param Translate $outputlangs object lang to use for translations
  2040. * @param int $hidedetails Hide details of lines
  2041. * @param int $hidedesc Hide description
  2042. * @param int $hideref Hide ref
  2043. * @param null|array $moreparams Array to provide more information
  2044. * @return int 0 if KO, 1 if OK
  2045. */
  2046. public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
  2047. {
  2048. global $conf,$langs;
  2049. $langs->load("sendings");
  2050. if (! dol_strlen($modele)) {
  2051. $modele = 'rouget';
  2052. if ($this->modelpdf) {
  2053. $modele = $this->modelpdf;
  2054. } elseif (! empty($conf->global->EXPEDITION_ADDON_PDF)) {
  2055. $modele = $conf->global->EXPEDITION_ADDON_PDF;
  2056. }
  2057. }
  2058. $modelpath = "core/modules/expedition/doc/";
  2059. $this->fetch_origin();
  2060. return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
  2061. }
  2062. /**
  2063. * Function used to replace a thirdparty id with another one.
  2064. *
  2065. * @param DoliDB $db Database handler
  2066. * @param int $origin_id Old thirdparty id
  2067. * @param int $dest_id New thirdparty id
  2068. * @return bool
  2069. */
  2070. public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
  2071. {
  2072. $tables = array(
  2073. 'expedition'
  2074. );
  2075. return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
  2076. }
  2077. }
  2078. /**
  2079. * Classe de gestion des lignes de bons d'expedition
  2080. */
  2081. class ExpeditionLigne extends CommonObjectLine
  2082. {
  2083. /**
  2084. * @var string ID to identify managed object
  2085. */
  2086. public $element='expeditiondet';
  2087. /**
  2088. * @var string Name of table without prefix where object is stored
  2089. */
  2090. public $table_element='expeditiondet';
  2091. /**
  2092. * @deprecated
  2093. * @see $fk_origin_line
  2094. */
  2095. public $origin_line_id;
  2096. /**
  2097. * @var int ID
  2098. */
  2099. public $fk_origin_line;
  2100. /**
  2101. * @var int Id of shipment
  2102. */
  2103. public $fk_expedition;
  2104. /**
  2105. * @var DoliDB Database handler.
  2106. */
  2107. public $db;
  2108. /**
  2109. * @var float qty asked From llx_expeditiondet
  2110. */
  2111. public $qty;
  2112. /**
  2113. * @var float qty shipped
  2114. */
  2115. public $qty_shipped;
  2116. /**
  2117. * @var int Id of product
  2118. */
  2119. public $fk_product;
  2120. public $detail_batch;
  2121. /**
  2122. * @var int Id of warehouse
  2123. */
  2124. public $entrepot_id;
  2125. /**
  2126. * @var float qty asked From llx_commandedet or llx_propaldet
  2127. */
  2128. public $qty_asked;
  2129. /**
  2130. * @deprecated
  2131. * @see $product_ref
  2132. */
  2133. public $ref;
  2134. /**
  2135. * @var string product ref
  2136. */
  2137. public $product_ref;
  2138. /**
  2139. * @deprecated
  2140. * @see $product_label
  2141. */
  2142. public $libelle;
  2143. /**
  2144. * @var string product label
  2145. */
  2146. public $product_label;
  2147. /**
  2148. * @var string product description
  2149. * @deprecated
  2150. * @see $product_desc
  2151. */
  2152. public $desc;
  2153. /**
  2154. * @var string product description
  2155. */
  2156. public $product_desc;
  2157. /**
  2158. * @var float weight
  2159. */
  2160. public $weight;
  2161. public $weight_units;
  2162. /**
  2163. * @var float weight
  2164. */
  2165. public $length;
  2166. public $length_units;
  2167. /**
  2168. * @var float weight
  2169. */
  2170. public $surface;
  2171. public $surface_units;
  2172. /**
  2173. * @var float weight
  2174. */
  2175. public $volume;
  2176. public $volume_units;
  2177. // Invoicing
  2178. public $remise_percent;
  2179. public $tva_tx;
  2180. /**
  2181. * @var float total without tax
  2182. */
  2183. public $total_ht;
  2184. /**
  2185. * @var float total with tax
  2186. */
  2187. public $total_ttc;
  2188. /**
  2189. * @var float total vat
  2190. */
  2191. public $total_tva;
  2192. /**
  2193. * @var float total localtax 1
  2194. */
  2195. public $total_localtax1;
  2196. /**
  2197. * @var float total localtax 2
  2198. */
  2199. public $total_localtax2;
  2200. /**
  2201. * Constructor
  2202. *
  2203. * @param DoliDB $db Database handler
  2204. */
  2205. public function __construct($db)
  2206. {
  2207. $this->db=$db;
  2208. }
  2209. /**
  2210. * Load line expedition
  2211. *
  2212. * @param int $rowid Id line order
  2213. * @return int <0 if KO, >0 if OK
  2214. */
  2215. public function fetch($rowid)
  2216. {
  2217. $sql = 'SELECT ed.rowid, ed.fk_expedition, ed.fk_entrepot, ed.fk_origin_line, ed.qty, ed.rang';
  2218. $sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as ed';
  2219. $sql.= ' WHERE ed.rowid = '.$rowid;
  2220. $result = $this->db->query($sql);
  2221. if ($result)
  2222. {
  2223. $objp = $this->db->fetch_object($result);
  2224. $this->id = $objp->rowid;
  2225. $this->fk_expedition = $objp->fk_expedition;
  2226. $this->entrepot_id = $objp->fk_entrepot;
  2227. $this->fk_origin_line = $objp->fk_origin_line;
  2228. $this->qty = $objp->qty;
  2229. $this->rang = $objp->rang;
  2230. $this->db->free($result);
  2231. return 1;
  2232. }
  2233. else
  2234. {
  2235. $this->errors[] = $this->db->lasterror();
  2236. $this->error = $this->db->lasterror();
  2237. return -1;
  2238. }
  2239. }
  2240. /**
  2241. * Insert line into database
  2242. *
  2243. * @param User $user User that modify
  2244. * @param int $notrigger 1 = disable triggers
  2245. * @return int <0 if KO, line id >0 if OK
  2246. */
  2247. public function insert($user, $notrigger = 0)
  2248. {
  2249. global $langs, $conf;
  2250. $error=0;
  2251. // Check parameters
  2252. if (empty($this->fk_expedition) || empty($this->fk_origin_line) || ! is_numeric($this->qty))
  2253. {
  2254. $this->error = 'ErrorMandatoryParametersNotProvided';
  2255. return -1;
  2256. }
  2257. $this->db->begin();
  2258. $sql = "INSERT INTO ".MAIN_DB_PREFIX."expeditiondet (";
  2259. $sql.= "fk_expedition";
  2260. $sql.= ", fk_entrepot";
  2261. $sql.= ", fk_origin_line";
  2262. $sql.= ", qty";
  2263. $sql.= ") VALUES (";
  2264. $sql.= $this->fk_expedition;
  2265. $sql.= ", ".(empty($this->entrepot_id) ? 'NULL' : $this->entrepot_id);
  2266. $sql.= ", ".$this->fk_origin_line;
  2267. $sql.= ", ".$this->qty;
  2268. $sql.= ")";
  2269. dol_syslog(get_class($this)."::insert", LOG_DEBUG);
  2270. $resql = $this->db->query($sql);
  2271. if ($resql)
  2272. {
  2273. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."expeditiondet");
  2274. if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))
  2275. {
  2276. $result=$this->insertExtraFields();
  2277. if ($result < 0)
  2278. {
  2279. $error++;
  2280. }
  2281. }
  2282. if (! $error && ! $notrigger)
  2283. {
  2284. // Call trigger
  2285. $result=$this->call_trigger('LINESHIPPING_INSERT', $user);
  2286. if ($result < 0)
  2287. {
  2288. $error++;
  2289. }
  2290. // End call triggers
  2291. }
  2292. if (! $error) {
  2293. $this->db->commit();
  2294. return $this->id;
  2295. }
  2296. foreach($this->errors as $errmsg)
  2297. {
  2298. dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
  2299. $this->error.=($this->error?', '.$errmsg:$errmsg);
  2300. }
  2301. $this->db->rollback();
  2302. return -1*$error;
  2303. }
  2304. else
  2305. {
  2306. $error++;
  2307. }
  2308. }
  2309. /**
  2310. * Delete shipment line.
  2311. *
  2312. * @param User $user User that modify
  2313. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  2314. * @return int >0 if OK, <0 if KO
  2315. */
  2316. public function delete($user = null, $notrigger = 0)
  2317. {
  2318. global $conf;
  2319. $error=0;
  2320. $this->db->begin();
  2321. // delete batch expedition line
  2322. if ($conf->productbatch->enabled)
  2323. {
  2324. $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
  2325. $sql.= " WHERE fk_expeditiondet = ".$this->id;
  2326. if (!$this->db->query($sql))
  2327. {
  2328. $this->errors[]=$this->db->lasterror()." - sql=$sql";
  2329. $error++;
  2330. }
  2331. }
  2332. $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet";
  2333. $sql.= " WHERE rowid = ".$this->id;
  2334. if (! $error && $this->db->query($sql))
  2335. {
  2336. // Remove extrafields
  2337. if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
  2338. {
  2339. $result=$this->deleteExtraFields();
  2340. if ($result < 0)
  2341. {
  2342. $this->errors[]=$this->error;
  2343. $error++;
  2344. }
  2345. }
  2346. if (! $error && ! $notrigger)
  2347. {
  2348. // Call trigger
  2349. $result=$this->call_trigger('LINESHIPPING_DELETE', $user);
  2350. if ($result < 0)
  2351. {
  2352. $this->errors[]=$this->error;
  2353. $error++;
  2354. }
  2355. // End call triggers
  2356. }
  2357. }
  2358. else
  2359. {
  2360. $this->errors[]=$this->db->lasterror()." - sql=$sql";
  2361. $error++;
  2362. }
  2363. if (! $error) {
  2364. $this->db->commit();
  2365. return 1;
  2366. }
  2367. else
  2368. {
  2369. foreach($this->errors as $errmsg)
  2370. {
  2371. dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
  2372. $this->error.=($this->error?', '.$errmsg:$errmsg);
  2373. }
  2374. $this->db->rollback();
  2375. return -1*$error;
  2376. }
  2377. }
  2378. /**
  2379. * Update a line in database
  2380. *
  2381. * @param User $user User that modify
  2382. * @param int $notrigger 1 = disable triggers
  2383. * @return int < 0 if KO, > 0 if OK
  2384. */
  2385. public function update($user = null, $notrigger = 0)
  2386. {
  2387. global $conf;
  2388. $error=0;
  2389. dol_syslog(get_class($this)."::update id=$this->id, entrepot_id=$this->entrepot_id, product_id=$this->fk_product, qty=$this->qty");
  2390. $this->db->begin();
  2391. // Clean parameters
  2392. if (empty($this->qty)) $this->qty=0;
  2393. $qty=price2num($this->qty);
  2394. $remainingQty = 0;
  2395. $batch = null;
  2396. $batch_id = null;
  2397. $expedition_batch_id = null;
  2398. if (is_array($this->detail_batch)) // array of ExpeditionLineBatch
  2399. {
  2400. if (count($this->detail_batch) > 1)
  2401. {
  2402. dol_syslog(get_class($this).'::update only possible for one batch', LOG_ERR);
  2403. $this->errors[]='ErrorBadParameters';
  2404. $error++;
  2405. }
  2406. else
  2407. {
  2408. $batch = $this->detail_batch[0]->batch;
  2409. $batch_id = $this->detail_batch[0]->fk_origin_stock;
  2410. $expedition_batch_id = $this->detail_batch[0]->id;
  2411. if ($this->entrepot_id != $this->detail_batch[0]->entrepot_id)
  2412. {
  2413. dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
  2414. $this->errors[]='ErrorBadParameters';
  2415. $error++;
  2416. }
  2417. $qty = price2num($this->detail_batch[0]->qty);
  2418. }
  2419. }
  2420. elseif (! empty($this->detail_batch))
  2421. {
  2422. $batch = $this->detail_batch->batch;
  2423. $batch_id = $this->detail_batch->fk_origin_stock;
  2424. $expedition_batch_id = $this->detail_batch->id;
  2425. if ($this->entrepot_id != $this->detail_batch->entrepot_id)
  2426. {
  2427. dol_syslog(get_class($this).'::update only possible for batch of same warehouse', LOG_ERR);
  2428. $this->errors[]='ErrorBadParameters';
  2429. $error++;
  2430. }
  2431. $qty = price2num($this->detail_batch->qty);
  2432. }
  2433. // check parameters
  2434. if (! isset($this->id) || ! isset($this->entrepot_id))
  2435. {
  2436. dol_syslog(get_class($this).'::update missing line id and/or warehouse id', LOG_ERR);
  2437. $this->errors[]='ErrorMandatoryParametersNotProvided';
  2438. $error++;
  2439. return -1;
  2440. }
  2441. // update lot
  2442. if (! empty($batch) && $conf->productbatch->enabled)
  2443. {
  2444. dol_syslog(get_class($this)."::update expedition batch id=$expedition_batch_id, batch_id=$batch_id, batch=$batch");
  2445. if (empty($batch_id) || empty($this->fk_product)) {
  2446. dol_syslog(get_class($this).'::update missing fk_origin_stock (batch_id) and/or fk_product', LOG_ERR);
  2447. $this->errors[]='ErrorMandatoryParametersNotProvided';
  2448. $error++;
  2449. }
  2450. // fetch remaining lot qty
  2451. require_once DOL_DOCUMENT_ROOT.'/expedition/class/expeditionbatch.class.php';
  2452. if (! $error && ($lotArray = ExpeditionLineBatch::fetchAll($this->db, $this->id)) < 0)
  2453. {
  2454. $this->errors[]=$this->db->lasterror()." - ExpeditionLineBatch::fetchAll";
  2455. $error++;
  2456. }
  2457. else
  2458. {
  2459. // caculate new total line qty
  2460. foreach ($lotArray as $lot)
  2461. {
  2462. if ($expedition_batch_id != $lot->id)
  2463. {
  2464. $remainingQty += $lot->qty;
  2465. }
  2466. }
  2467. $qty += $remainingQty;
  2468. //fetch lot details
  2469. // fetch from product_lot
  2470. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
  2471. $lot = new Productlot($this->db);
  2472. if ($lot->fetch(0, $this->fk_product, $batch) < 0)
  2473. {
  2474. $this->errors[] = $lot->errors;
  2475. $error++;
  2476. }
  2477. if (! $error && ! empty($expedition_batch_id))
  2478. {
  2479. // delete lot expedition line
  2480. $sql = "DELETE FROM ".MAIN_DB_PREFIX."expeditiondet_batch";
  2481. $sql.= " WHERE fk_expeditiondet = ".$this->id;
  2482. $sql.= " AND rowid = ".$expedition_batch_id;
  2483. if (!$this->db->query($sql))
  2484. {
  2485. $this->errors[]=$this->db->lasterror()." - sql=$sql";
  2486. $error++;
  2487. }
  2488. }
  2489. if (! $error && $this->detail_batch->qty > 0)
  2490. {
  2491. // create lot expedition line
  2492. if (isset($lot->id))
  2493. {
  2494. $shipmentLot = new ExpeditionLineBatch($this->db);
  2495. $shipmentLot->batch = $lot->batch;
  2496. $shipmentLot->eatby = $lot->eatby;
  2497. $shipmentLot->sellby = $lot->sellby;
  2498. $shipmentLot->entrepot_id = $this->detail_batch->entrepot_id;
  2499. $shipmentLot->qty = $this->detail_batch->qty;
  2500. $shipmentLot->fk_origin_stock = $batch_id;
  2501. if ($shipmentLot->create($this->id) < 0)
  2502. {
  2503. $this->errors[]=$shipmentLot->errors;
  2504. $error++;
  2505. }
  2506. }
  2507. }
  2508. }
  2509. }
  2510. if (! $error)
  2511. {
  2512. // update line
  2513. $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
  2514. $sql.= " fk_entrepot = ".($this->entrepot_id > 0 ? $this->entrepot_id : 'null');
  2515. $sql.= " , qty = ".$qty;
  2516. $sql.= " WHERE rowid = ".$this->id;
  2517. if (!$this->db->query($sql))
  2518. {
  2519. $this->errors[]=$this->db->lasterror()." - sql=$sql";
  2520. $error++;
  2521. }
  2522. }
  2523. if (! $error)
  2524. {
  2525. if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
  2526. {
  2527. $result=$this->insertExtraFields();
  2528. if ($result < 0)
  2529. {
  2530. $this->errors[]=$this->error;
  2531. $error++;
  2532. }
  2533. }
  2534. }
  2535. if (! $error && ! $notrigger)
  2536. {
  2537. // Call trigger
  2538. $result=$this->call_trigger('LINESHIPPING_UPDATE', $user);
  2539. if ($result < 0)
  2540. {
  2541. $this->errors[]=$this->error;
  2542. $error++;
  2543. }
  2544. // End call triggers
  2545. }
  2546. if (!$error) {
  2547. $this->db->commit();
  2548. return 1;
  2549. }
  2550. else
  2551. {
  2552. foreach($this->errors as $errmsg)
  2553. {
  2554. dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
  2555. $this->error.=($this->error?', '.$errmsg:$errmsg);
  2556. }
  2557. $this->db->rollback();
  2558. return -1*$error;
  2559. }
  2560. }
  2561. }