expedition.class.php 84 KB

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