expedition.class.php 94 KB

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