fournisseur.commande.class.php 142 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155
  1. <?php
  2. /* Copyright (C) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
  4. * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
  5. * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
  6. * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
  7. * Copyright (C) 2010-2018 Philippe Grand <philippe.grand@atoo-net.com>
  8. * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
  9. * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
  10. * Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr>
  11. * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
  12. * Copyright (C) 2018-2023 Frédéric France <frederic.france@netlogic.fr>
  13. * Copyright (C) 2018-2022 Ferran Marcet <fmarcet@2byte.es>
  14. * Copyright (C) 2021 Josep Lluís Amador <joseplluis@lliuretic.cat>
  15. * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
  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/fourn/class/fournisseur.commande.class.php
  32. * \ingroup fournisseur,commande
  33. * \brief File of class to manage suppliers orders
  34. */
  35. require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
  36. require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
  37. require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
  38. if (isModEnabled('productbatch')) {
  39. require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
  40. }
  41. /**
  42. * Class to manage predefined suppliers products
  43. */
  44. class CommandeFournisseur extends CommonOrder
  45. {
  46. /**
  47. * @var string ID to identify managed object
  48. */
  49. public $element = 'order_supplier';
  50. /**
  51. * @var string Name of table without prefix where object is stored
  52. */
  53. public $table_element = 'commande_fournisseur';
  54. /**
  55. * @var string Name of subtable line
  56. */
  57. public $table_element_line = 'commande_fournisseurdet';
  58. /**
  59. * @var string Field with ID of parent key if this field has a parent
  60. */
  61. public $fk_element = 'fk_commande';
  62. /**
  63. * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
  64. */
  65. public $picto = 'supplier_order';
  66. /**
  67. * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  68. * @var int
  69. */
  70. public $ismultientitymanaged = 1;
  71. /**
  72. * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
  73. * @var integer
  74. */
  75. public $restrictiononfksoc = 1;
  76. /**
  77. * {@inheritdoc}
  78. */
  79. protected $table_ref_field = 'ref';
  80. /**
  81. * @var int ID
  82. */
  83. public $id;
  84. /**
  85. * Supplier order reference
  86. * @var string
  87. */
  88. public $ref;
  89. /**
  90. * @var string ref supplier
  91. */
  92. public $ref_supplier;
  93. /**
  94. * @var string ref supplier
  95. * @deprecated
  96. * @see $ref_supplier
  97. */
  98. public $ref_fourn;
  99. public $brouillon;
  100. /**
  101. * @var int
  102. */
  103. public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process runing -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
  104. // -> 7=Canceled/Never received -> (reopen) 3=Process runing
  105. // -> 6=Canceled -> (reopen) 2=Approved
  106. // -> 9=Refused -> (reopen) 1=Validated
  107. // Note: billed or not is on another field "billed"
  108. /**
  109. * @var array List of status
  110. */
  111. public $statuts;
  112. /**
  113. * @var array List of status short
  114. */
  115. public $statuts_short;
  116. public $billed;
  117. public $socid;
  118. public $fourn_id;
  119. public $date;
  120. public $date_creation;
  121. public $date_valid;
  122. public $date_approve;
  123. public $date_approve2; // Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
  124. public $date_commande;
  125. /**
  126. * @var int Date expected for delivery
  127. * @deprecated See delivery_date
  128. */
  129. public $date_livraison;
  130. /**
  131. * @var int Date expected for delivery
  132. */
  133. public $delivery_date;
  134. public $total_ht;
  135. public $total_tva;
  136. public $total_localtax1; // Total Local tax 1
  137. public $total_localtax2; // Total Local tax 2
  138. public $total_ttc;
  139. public $source;
  140. /**
  141. * @var int ID
  142. */
  143. public $fk_project;
  144. public $cond_reglement_id;
  145. public $cond_reglement_code;
  146. public $cond_reglement_label; // Label
  147. public $cond_reglement_doc; // Label on documents
  148. /**
  149. * @var int ID
  150. */
  151. public $fk_account;
  152. /**
  153. * @var int payment choice ID
  154. */
  155. public $mode_reglement_id;
  156. /**
  157. * @var string payment choice code
  158. */
  159. public $mode_reglement_code;
  160. /**
  161. * @var string paymnet choice label
  162. */
  163. public $mode_reglement;
  164. public $user_author_id;
  165. public $user_valid_id;
  166. public $user_approve_id;
  167. public $user_approve_id2; // Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
  168. public $refuse_note;
  169. public $extraparams = array();
  170. /**
  171. * @var CommandeFournisseurLigne[]
  172. */
  173. public $lines = array();
  174. /**
  175. * @var CommandeFournisseurLigne
  176. */
  177. public $line;
  178. // Add for supplier_proposal
  179. public $origin;
  180. public $origin_id;
  181. public $linked_objects = array();
  182. // Multicurrency
  183. /**
  184. * @var int ID
  185. */
  186. public $fk_multicurrency;
  187. public $multicurrency_code;
  188. public $multicurrency_tx;
  189. public $multicurrency_total_ht;
  190. public $multicurrency_total_tva;
  191. public $multicurrency_total_ttc;
  192. /**
  193. * 'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:Sortfield]]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
  194. * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
  195. * 'label' the translation key.
  196. * 'picto' is code of a picto to show before value in forms
  197. * 'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM' or 'isModEnabled("multicurrency")' ...)
  198. * 'position' is the sort order of field.
  199. * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
  200. * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
  201. * 'noteditable' says if field is not editable (1 or 0)
  202. * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
  203. * 'index' if we want an index in database.
  204. * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
  205. * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
  206. * 'isameasure' must be set to 1 or 2 if field can be used for measure. Field type must be summable like integer or double(24,8). Use 1 in most cases, or 2 if you don't want to see the column total into list (for example for percentage)
  207. * 'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'css'=>'minwidth300 maxwidth500 widthcentpercentminusx', 'cssview'=>'wordbreak', 'csslist'=>'tdoverflowmax200'
  208. * 'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
  209. * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
  210. * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
  211. * 'arrayofkeyval' to set a list of values if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel"). Note that type can be 'integer' or 'varchar'
  212. * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
  213. * 'comment' is not used. You can store here any text of your choice. It is not used by application.
  214. * 'validate' is 1 if need to validate with $this->validateField()
  215. * 'copytoclipboard' is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value)
  216. *
  217. * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
  218. */
  219. public $fields = array(
  220. 'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>10),
  221. 'ref' =>array('type'=>'varchar(255)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>25, 'searchall'=>1),
  222. 'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'Ref ext', 'enabled'=>1, 'visible'=>0, 'position'=>35),
  223. 'ref_supplier' =>array('type'=>'varchar(255)', 'label'=>'RefOrderSupplierShort', 'enabled'=>1, 'visible'=>1, 'position'=>40, 'searchall'=>1),
  224. 'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label'=>'Project', 'enabled'=>"isModEnabled('project')", 'visible'=>-1, 'position'=>45),
  225. 'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>710),
  226. 'date_approve' =>array('type'=>'datetime', 'label'=>'DateApprove', 'enabled'=>1, 'visible'=>-1, 'position'=>720),
  227. 'date_approve2' =>array('type'=>'datetime', 'label'=>'DateApprove2', 'enabled'=>1, 'visible'=>3, 'position'=>725),
  228. 'date_commande' =>array('type'=>'date', 'label'=>'OrderDateShort', 'enabled'=>1, 'visible'=>1, 'position'=>70),
  229. 'date_livraison' =>array('type'=>'datetime', 'label'=>'DeliveryDate', 'enabled'=>'empty($conf->global->ORDER_DISABLE_DELIVERY_DATE)', 'visible'=>1, 'position'=>74),
  230. 'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>3, 'position'=>41),
  231. 'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>3, 'notnull'=>-1, 'position'=>80),
  232. 'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>3, 'position'=>711),
  233. 'fk_user_approve' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserApproval', 'enabled'=>1, 'visible'=>3, 'position'=>721),
  234. 'fk_user_approve2' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserApproval2', 'enabled'=>1, 'visible'=>3, 'position'=>726),
  235. 'source' =>array('type'=>'smallint(6)', 'label'=>'Source', 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>100),
  236. 'billed' =>array('type'=>'smallint(6)', 'label'=>'Billed', 'enabled'=>1, 'visible'=>1, 'position'=>710),
  237. 'total_ht' =>array('type'=>'double(24,8)', 'label'=>'AmountHT', 'enabled'=>1, 'visible'=>1, 'position'=>130, 'isameasure'=>1),
  238. 'total_tva' =>array('type'=>'double(24,8)', 'label'=>'AmountVAT', 'enabled'=>1, 'visible'=>1, 'position'=>135, 'isameasure'=>1),
  239. 'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LT1', 'enabled'=>1, 'visible'=>3, 'position'=>140, 'isameasure'=>1),
  240. 'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LT2', 'enabled'=>1, 'visible'=>3, 'position'=>145, 'isameasure'=>1),
  241. 'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'AmountTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>150, 'isameasure'=>1),
  242. 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>750, 'searchall'=>1),
  243. 'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>760, 'searchall'=>1),
  244. 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPDF', 'enabled'=>1, 'visible'=>0, 'position'=>165),
  245. 'fk_input_method' =>array('type'=>'integer', 'label'=>'OrderMode', 'enabled'=>1, 'visible'=>3, 'position'=>170),
  246. 'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>3, 'position'=>175),
  247. 'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>3, 'position'=>180),
  248. 'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>0, 'position'=>190),
  249. 'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'isModEnabled("banque")', 'visible'=>3, 'position'=>200),
  250. 'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>1, 'visible'=>3, 'position'=>205),
  251. 'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLocation', 'enabled'=>1, 'visible'=>3, 'position'=>210),
  252. 'fk_multicurrency' =>array('type'=>'integer', 'label'=>'Fk multicurrency', 'enabled'=>1, 'visible'=>0, 'position'=>215),
  253. 'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'Currency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>220),
  254. 'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'CurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>225),
  255. 'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>230),
  256. 'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
  257. 'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240),
  258. 'date_creation' =>array('type'=>'datetime', 'label'=>'Date creation', 'enabled'=>1, 'visible'=>-1, 'position'=>500),
  259. 'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'isModEnabled("societe")', 'visible'=>1, 'notnull'=>1, 'position'=>50),
  260. 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>1000, 'index'=>1),
  261. 'tms'=>array('type'=>'datetime', 'label'=>"DateModificationShort", 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>501),
  262. 'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>0, 'position'=>700),
  263. 'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'position'=>701),
  264. 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>0, 'position'=>900),
  265. );
  266. /**
  267. * Draft status
  268. */
  269. const STATUS_DRAFT = 0;
  270. /**
  271. * Validated status
  272. */
  273. const STATUS_VALIDATED = 1;
  274. /**
  275. * Accepted
  276. */
  277. const STATUS_ACCEPTED = 2;
  278. /**
  279. * Order sent, shipment on process
  280. */
  281. const STATUS_ORDERSENT = 3;
  282. /**
  283. * Received partially
  284. */
  285. const STATUS_RECEIVED_PARTIALLY = 4;
  286. /**
  287. * Received completely
  288. */
  289. const STATUS_RECEIVED_COMPLETELY = 5;
  290. /**
  291. * Order canceled
  292. */
  293. const STATUS_CANCELED = 6;
  294. /**
  295. * Order canceled/never received
  296. */
  297. const STATUS_CANCELED_AFTER_ORDER = 7;
  298. /**
  299. * Refused
  300. */
  301. const STATUS_REFUSED = 9;
  302. /**
  303. * The constant used into source field to track the order was generated by the replenishement feature
  304. */
  305. const SOURCE_ID_REPLENISHMENT = 42;
  306. /**
  307. * Constructor
  308. *
  309. * @param DoliDB $db Database handler
  310. */
  311. public function __construct($db)
  312. {
  313. $this->db = $db;
  314. }
  315. /**
  316. * Get object and lines from database
  317. *
  318. * @param int $id Id of order to load
  319. * @param string $ref Ref of object
  320. * @return int >0 if OK, <0 if KO, 0 if not found
  321. */
  322. public function fetch($id, $ref = '')
  323. {
  324. global $conf;
  325. // Check parameters
  326. if (empty($id) && empty($ref)) {
  327. return -1;
  328. }
  329. $sql = "SELECT c.rowid, c.entity, c.ref, ref_supplier, c.fk_soc, c.fk_statut, c.amount_ht, c.total_ht, c.total_ttc, c.total_tva,";
  330. $sql .= " c.localtax1, c.localtax2, ";
  331. $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
  332. $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_approve, c.fk_user_approve2,";
  333. $sql .= " c.date_commande as date_commande, c.date_livraison as delivery_date, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_projet as fk_project, c.remise_percent, c.source, c.fk_input_method,";
  334. $sql .= " c.fk_account,";
  335. $sql .= " c.note_private, c.note_public, c.model_pdf, c.extraparams, c.billed,";
  336. $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
  337. $sql .= " cm.libelle as methode_commande,";
  338. $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
  339. $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
  340. $sql .= ', c.fk_incoterms, c.location_incoterms';
  341. $sql .= ', i.libelle as label_incoterms';
  342. $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur as c";
  343. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
  344. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as p ON c.fk_mode_reglement = p.id";
  345. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_input_method as cm ON cm.rowid = c.fk_input_method";
  346. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
  347. if (empty($id)) {
  348. $sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
  349. } else {
  350. $sql .= " WHERE c.rowid=".((int) $id);
  351. }
  352. if ($ref) {
  353. $sql .= " AND c.ref='".$this->db->escape($ref)."'";
  354. }
  355. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  356. $resql = $this->db->query($sql);
  357. if ($resql) {
  358. $obj = $this->db->fetch_object($resql);
  359. if (!$obj) {
  360. $this->error = 'Bill with id '.$id.' not found';
  361. dol_syslog(get_class($this).'::fetch '.$this->error);
  362. return 0;
  363. }
  364. $this->id = $obj->rowid;
  365. $this->entity = $obj->entity;
  366. $this->ref = $obj->ref;
  367. $this->ref_supplier = $obj->ref_supplier;
  368. $this->socid = $obj->fk_soc;
  369. $this->fourn_id = $obj->fk_soc;
  370. $this->statut = $obj->fk_statut;
  371. $this->status = $obj->fk_statut;
  372. $this->billed = $obj->billed;
  373. $this->user_author_id = $obj->fk_user_author;
  374. $this->user_valid_id = $obj->fk_user_valid;
  375. $this->user_approve_id = $obj->fk_user_approve;
  376. $this->user_approve_id2 = $obj->fk_user_approve2;
  377. $this->total_ht = $obj->total_ht;
  378. $this->total_tva = $obj->total_tva;
  379. $this->total_localtax1 = $obj->localtax1;
  380. $this->total_localtax2 = $obj->localtax2;
  381. $this->total_ttc = $obj->total_ttc;
  382. $this->date_creation = $this->db->jdate($obj->date_creation);
  383. $this->date_valid = $this->db->jdate($obj->date_valid);
  384. $this->date_approve = $this->db->jdate($obj->date_approve);
  385. $this->date_approve2 = $this->db->jdate($obj->date_approve2);
  386. $this->date_commande = $this->db->jdate($obj->date_commande); // date we make the order to supplier
  387. if (isset($obj->date_commande)) {
  388. $this->date = $this->date_commande;
  389. } else {
  390. $this->date = $this->date_creation;
  391. }
  392. $this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
  393. $this->delivery_date = $this->db->jdate($obj->delivery_date);
  394. $this->remise_percent = $obj->remise_percent;
  395. $this->methode_commande_id = $obj->fk_input_method;
  396. $this->methode_commande = $obj->methode_commande;
  397. $this->source = $obj->source;
  398. $this->fk_project = $obj->fk_project;
  399. $this->cond_reglement_id = $obj->fk_cond_reglement;
  400. $this->cond_reglement_code = $obj->cond_reglement_code;
  401. $this->cond_reglement = $obj->cond_reglement_label; // deprecated
  402. $this->cond_reglement_label = $obj->cond_reglement_label;
  403. $this->cond_reglement_doc = $obj->cond_reglement_doc;
  404. $this->fk_account = $obj->fk_account;
  405. $this->mode_reglement_id = $obj->fk_mode_reglement;
  406. $this->mode_reglement_code = $obj->mode_reglement_code;
  407. $this->mode_reglement = $obj->mode_reglement_libelle;
  408. $this->note = $obj->note_private; // deprecated
  409. $this->note_private = $obj->note_private;
  410. $this->note_public = $obj->note_public;
  411. $this->model_pdf = $obj->model_pdf;
  412. $this->modelpdf = $obj->model_pdf; // deprecated
  413. //Incoterms
  414. $this->fk_incoterms = $obj->fk_incoterms;
  415. $this->location_incoterms = $obj->location_incoterms;
  416. $this->label_incoterms = $obj->label_incoterms;
  417. // Multicurrency
  418. $this->fk_multicurrency = $obj->fk_multicurrency;
  419. $this->multicurrency_code = $obj->multicurrency_code;
  420. $this->multicurrency_tx = $obj->multicurrency_tx;
  421. $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
  422. $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
  423. $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
  424. $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
  425. $this->db->free($resql);
  426. // Retrieve all extrafield
  427. // fetch optionals attributes and labels
  428. $this->fetch_optionals();
  429. if ($this->statut == 0) {
  430. $this->brouillon = 1;
  431. }
  432. /*
  433. * Lines
  434. */
  435. $result = $this->fetch_lines();
  436. if ($result < 0) {
  437. return -1;
  438. } else {
  439. return 1;
  440. }
  441. } else {
  442. $this->error = $this->db->error()." sql=".$sql;
  443. return -1;
  444. }
  445. }
  446. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  447. /**
  448. * Load array lines
  449. *
  450. * @param int $only_product Return only physical products
  451. * @return int <0 if KO, >0 if OK
  452. */
  453. public function fetch_lines($only_product = 0)
  454. {
  455. global $conf;
  456. // phpcs:enable
  457. $this->lines = array();
  458. $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
  459. $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice,";
  460. $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
  461. $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.special_code, l.fk_parent_line, l.rang,";
  462. $sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.description as product_desc, p.tobatch as product_tobatch, p.barcode as product_barcode,";
  463. $sql .= " l.fk_unit,";
  464. $sql .= " l.date_start, l.date_end,";
  465. $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc';
  466. $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as l";
  467. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
  468. $sql .= " WHERE l.fk_commande = ".((int) $this->id);
  469. if ($only_product) {
  470. $sql .= ' AND p.fk_product_type = 0';
  471. }
  472. $sql .= " ORDER BY l.rang, l.rowid";
  473. //print $sql;
  474. dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
  475. $result = $this->db->query($sql);
  476. if ($result) {
  477. $num = $this->db->num_rows($result);
  478. $i = 0;
  479. while ($i < $num) {
  480. $objp = $this->db->fetch_object($result);
  481. $line = new CommandeFournisseurLigne($this->db);
  482. $line->id = $objp->rowid;
  483. $line->fk_commande = $objp->fk_commande;
  484. $line->desc = $objp->description;
  485. $line->description = $objp->description;
  486. $line->qty = $objp->qty;
  487. $line->tva_tx = $objp->tva_tx;
  488. $line->localtax1_tx = $objp->localtax1_tx;
  489. $line->localtax2_tx = $objp->localtax2_tx;
  490. $line->localtax1_type = $objp->localtax1_type;
  491. $line->localtax2_type = $objp->localtax2_type;
  492. $line->subprice = $objp->subprice;
  493. $line->pu_ht = $objp->subprice;
  494. $line->remise_percent = $objp->remise_percent;
  495. $line->vat_src_code = $objp->vat_src_code;
  496. $line->total_ht = $objp->total_ht;
  497. $line->total_tva = $objp->total_tva;
  498. $line->total_localtax1 = $objp->total_localtax1;
  499. $line->total_localtax2 = $objp->total_localtax2;
  500. $line->total_ttc = $objp->total_ttc;
  501. $line->product_type = $objp->product_type;
  502. $line->fk_product = $objp->fk_product;
  503. $line->libelle = $objp->product_label; // deprecated
  504. $line->product_label = $objp->product_label;
  505. $line->product_desc = $objp->product_desc;
  506. $line->product_tobatch = $objp->product_tobatch;
  507. $line->product_barcode = $objp->product_barcode;
  508. $line->ref = $objp->product_ref; // Ref of product
  509. $line->product_ref = $objp->product_ref; // Ref of product
  510. $line->ref_fourn = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
  511. $line->ref_supplier = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
  512. if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
  513. // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
  514. // Move this into another method and call it when required.
  515. // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
  516. $sqlsearchpackage = 'SELECT rowid, packaging FROM '.MAIN_DB_PREFIX."product_fournisseur_price";
  517. $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('product_fournisseur_price').")";
  518. $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
  519. $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
  520. $sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty); // required to be qualified
  521. $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")"; // required to be qualified
  522. $sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
  523. $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
  524. $sqlsearchpackage .= " LIMIT 1";
  525. $resqlsearchpackage = $this->db->query($sqlsearchpackage);
  526. if ($resqlsearchpackage) {
  527. $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
  528. if ($objsearchpackage) {
  529. $line->fk_fournprice = $objsearchpackage->rowid;
  530. $line->packaging = $objsearchpackage->packaging;
  531. }
  532. } else {
  533. $this->error = $this->db->lasterror();
  534. return -1;
  535. }
  536. }
  537. $line->date_start = $this->db->jdate($objp->date_start);
  538. $line->date_end = $this->db->jdate($objp->date_end);
  539. $line->fk_unit = $objp->fk_unit;
  540. // Multicurrency
  541. $line->fk_multicurrency = $objp->fk_multicurrency;
  542. $line->multicurrency_code = $objp->multicurrency_code;
  543. $line->multicurrency_subprice = $objp->multicurrency_subprice;
  544. $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
  545. $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
  546. $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
  547. $line->special_code = $objp->special_code;
  548. $line->fk_parent_line = $objp->fk_parent_line;
  549. $line->rang = $objp->rang;
  550. // Retrieve all extrafield
  551. // fetch optionals attributes and labels
  552. $line->fetch_optionals();
  553. $this->lines[$i] = $line;
  554. $i++;
  555. }
  556. $this->db->free($result);
  557. return $num;
  558. } else {
  559. $this->error = $this->db->error()." sql=".$sql;
  560. return -1;
  561. }
  562. }
  563. /**
  564. * Validate an order
  565. *
  566. * @param User $user Validator User
  567. * @param int $idwarehouse Id of warehouse to use for stock decrease
  568. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  569. * @return int <0 if KO, >0 if OK
  570. */
  571. public function valid($user, $idwarehouse = 0, $notrigger = 0)
  572. {
  573. global $langs, $conf;
  574. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  575. $error = 0;
  576. dol_syslog(get_class($this)."::valid");
  577. $result = 0;
  578. if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
  579. || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))) {
  580. $this->db->begin();
  581. // Definition of supplier order numbering model name
  582. $soc = new Societe($this->db);
  583. $soc->fetch($this->fourn_id);
  584. // Check if object has a temporary ref
  585. if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
  586. $num = $this->getNextNumRef($soc);
  587. } else {
  588. $num = $this->ref;
  589. }
  590. $this->newref = dol_sanitizeFileName($num);
  591. $sql = 'UPDATE '.MAIN_DB_PREFIX."commande_fournisseur";
  592. $sql .= " SET ref='".$this->db->escape($num)."',";
  593. $sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
  594. $sql .= " date_valid='".$this->db->idate(dol_now())."',";
  595. $sql .= " fk_user_valid = ".((int) $user->id);
  596. $sql .= " WHERE rowid = ".((int) $this->id);
  597. $sql .= " AND fk_statut = ".self::STATUS_DRAFT;
  598. $resql = $this->db->query($sql);
  599. if (!$resql) {
  600. dol_print_error($this->db);
  601. $error++;
  602. }
  603. if (!$error && !$notrigger) {
  604. // Call trigger
  605. $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
  606. if ($result < 0) {
  607. $error++;
  608. }
  609. // End call triggers
  610. }
  611. if (!$error) {
  612. $this->oldref = $this->ref;
  613. // Rename directory if dir was a temporary ref
  614. if (preg_match('/^[\(]?PROV/i', $this->ref)) {
  615. // Now we rename also files into index
  616. $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
  617. $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
  618. $resql = $this->db->query($sql);
  619. if (!$resql) {
  620. $error++; $this->error = $this->db->lasterror();
  621. }
  622. // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
  623. $oldref = dol_sanitizeFileName($this->ref);
  624. $newref = dol_sanitizeFileName($num);
  625. $dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
  626. $dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
  627. if (!$error && file_exists($dirsource)) {
  628. dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
  629. if (@rename($dirsource, $dirdest)) {
  630. dol_syslog("Rename ok");
  631. // Rename docs starting with $oldref with $newref
  632. $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
  633. foreach ($listoffiles as $fileentry) {
  634. $dirsource = $fileentry['name'];
  635. $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
  636. $dirsource = $fileentry['path'].'/'.$dirsource;
  637. $dirdest = $fileentry['path'].'/'.$dirdest;
  638. @rename($dirsource, $dirdest);
  639. }
  640. }
  641. }
  642. }
  643. }
  644. if (!$error) {
  645. $result = 1;
  646. $this->statut = self::STATUS_VALIDATED;
  647. $this->ref = $num;
  648. }
  649. if (!$error) {
  650. $this->db->commit();
  651. return 1;
  652. } else {
  653. $this->db->rollback();
  654. return -1;
  655. }
  656. } else {
  657. $this->error = 'NotAuthorized';
  658. dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
  659. return -1;
  660. }
  661. }
  662. /**
  663. * Return label of the status of object
  664. *
  665. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=short label + picto
  666. * @return string Label
  667. */
  668. public function getLibStatut($mode = 0)
  669. {
  670. return $this->LibStatut($this->statut, $mode, $this->billed);
  671. }
  672. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  673. /**
  674. * Return label of a status
  675. *
  676. * @param int $status Id statut
  677. * @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
  678. * @param int $billed 1=Billed
  679. * @return string Label of status
  680. */
  681. public function LibStatut($status, $mode = 0, $billed = 0)
  682. {
  683. // phpcs:enable
  684. global $conf, $langs, $hookmanager;
  685. if (empty($this->statuts) || empty($this->statuts_short)) {
  686. $langs->load('orders');
  687. $this->statuts[0] = 'StatusSupplierOrderDraft';
  688. $this->statuts[1] = 'StatusSupplierOrderValidated';
  689. $this->statuts[2] = 'StatusSupplierOrderApproved';
  690. if (empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
  691. $this->statuts[3] = 'StatusSupplierOrderOnProcess';
  692. } else {
  693. $this->statuts[3] = 'StatusSupplierOrderOnProcessWithValidation';
  694. }
  695. $this->statuts[4] = 'StatusSupplierOrderReceivedPartially';
  696. $this->statuts[5] = 'StatusSupplierOrderReceivedAll';
  697. $this->statuts[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
  698. $this->statuts[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
  699. $this->statuts[9] = 'StatusSupplierOrderRefused';
  700. // List of language codes for status
  701. $this->statuts_short[0] = 'StatusSupplierOrderDraftShort';
  702. $this->statuts_short[1] = 'StatusSupplierOrderValidatedShort';
  703. $this->statuts_short[2] = 'StatusSupplierOrderApprovedShort';
  704. $this->statuts_short[3] = 'StatusSupplierOrderOnProcessShort';
  705. $this->statuts_short[4] = 'StatusSupplierOrderReceivedPartiallyShort';
  706. $this->statuts_short[5] = 'StatusSupplierOrderReceivedAllShort';
  707. $this->statuts_short[6] = 'StatusSupplierOrderCanceledShort';
  708. $this->statuts_short[7] = 'StatusSupplierOrderCanceledShort';
  709. $this->statuts_short[9] = 'StatusSupplierOrderRefusedShort';
  710. }
  711. $statustrans = array(
  712. 0 => 'status0',
  713. 1 => 'status1b',
  714. 2 => 'status1',
  715. 3 => 'status4',
  716. 4 => 'status4b',
  717. 5 => 'status6',
  718. 6 => 'status9',
  719. 7 => 'status9',
  720. 9 => 'status9',
  721. );
  722. $statusClass = 'status0';
  723. if (!empty($statustrans[$status])) {
  724. $statusClass = $statustrans[$status];
  725. }
  726. $billedtext = '';
  727. if ($billed) {
  728. $billedtext = ' - '.$langs->trans("Billed");
  729. }
  730. if ($status == 5 && $billed) {
  731. $statusClass = 'status6';
  732. }
  733. $statusLong = $langs->transnoentitiesnoconv($this->statuts[$status]).$billedtext;
  734. $statusShort = $langs->transnoentitiesnoconv($this->statuts_short[$status]);
  735. $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
  736. $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
  737. if ($reshook > 0) {
  738. return $hookmanager->resPrint;
  739. }
  740. return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
  741. }
  742. /**
  743. * getTooltipContentArray
  744. *
  745. * @param array $params ex option, infologin
  746. * @since v18
  747. * @return array
  748. */
  749. public function getTooltipContentArray($params)
  750. {
  751. global $conf, $langs, $user;
  752. $langs->loadLangs(['bills', 'orders']);
  753. $datas = [];
  754. $nofetch = !empty($params['nofetch']);
  755. if ($user->hasRight("fournisseur", "commande", "read")) {
  756. $datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
  757. if (isset($this->statut)) {
  758. $datas['picto'] .= ' '.$this->getLibStatut(5);
  759. }
  760. if (!empty($this->ref)) {
  761. $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
  762. }
  763. if (!empty($this->ref_supplier)) {
  764. $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
  765. }
  766. if (!$nofetch) {
  767. $langs->load('companies');
  768. if (empty($this->thirdparty)) {
  769. $this->fetch_thirdparty();
  770. }
  771. $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
  772. }
  773. if (!empty($this->total_ht)) {
  774. $datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
  775. }
  776. if (!empty($this->total_tva)) {
  777. $datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
  778. }
  779. if (!empty($this->total_ttc)) {
  780. $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
  781. }
  782. if (!empty($this->date)) {
  783. $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
  784. }
  785. if (!empty($this->delivery_date)) {
  786. $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
  787. }
  788. }
  789. return $datas;
  790. }
  791. /**
  792. * Return clicable name (with picto eventually)
  793. *
  794. * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto
  795. * @param string $option On what the link points
  796. * @param int $notooltip 1=Disable tooltip
  797. * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
  798. * @param int $addlinktonotes Add link to show notes
  799. * @return string Chain with URL
  800. */
  801. public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
  802. {
  803. global $langs, $conf, $user, $hookmanager;
  804. $result = '';
  805. $params = [
  806. 'id' => $this->id,
  807. 'objecttype' => $this->element,
  808. 'option' => $option,
  809. 'nofetch' => 1
  810. ];
  811. $classfortooltip = 'classfortooltip';
  812. $dataparams = '';
  813. if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
  814. $classfortooltip = 'classforajaxtooltip';
  815. $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
  816. $label = '';
  817. } else {
  818. $label = implode($this->getTooltipContentArray($params));
  819. }
  820. $url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
  821. if ($option !== 'nolink') {
  822. // Add param to save lastsearch_values or not
  823. $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
  824. if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
  825. $add_save_lastsearch_values = 1;
  826. }
  827. if ($add_save_lastsearch_values) {
  828. $url .= '&save_lastsearch_values=1';
  829. }
  830. }
  831. $linkclose = '';
  832. if (empty($notooltip)) {
  833. if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
  834. $label = $langs->trans("ShowOrder");
  835. $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
  836. }
  837. $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
  838. $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
  839. }
  840. $linkstart = '<a href="'.$url.'"';
  841. $linkstart .= $linkclose.'>';
  842. $linkend = '</a>';
  843. $result .= $linkstart;
  844. if ($withpicto) {
  845. $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : $dataparams.' class="'.(($withpicto != 2) ? 'paddingright ' : '').$classfortooltip.'"'), 0, 0, $notooltip ? 0 : 1);
  846. }
  847. if ($withpicto != 2) {
  848. $result .= $this->ref;
  849. }
  850. $result .= $linkend;
  851. if ($addlinktonotes) {
  852. $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
  853. if ($txttoshow) {
  854. $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
  855. $result .= ' <span class="note inline-block">';
  856. $result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
  857. $result .= img_picto('', 'note');
  858. $result .= '</a>';
  859. //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
  860. //$result.='</a>';
  861. $result .= '</span>';
  862. }
  863. }
  864. global $action;
  865. $hookmanager->initHooks(array($this->element . 'dao'));
  866. $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
  867. $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  868. if ($reshook > 0) {
  869. $result = $hookmanager->resPrint;
  870. } else {
  871. $result .= $hookmanager->resPrint;
  872. }
  873. return $result;
  874. }
  875. /**
  876. * Returns the following order reference not used depending on the numbering model activated
  877. * defined within COMMANDE_SUPPLIER_ADDON_NUMBER
  878. *
  879. * @param Societe $soc company object
  880. * @return string free reference for the invoice
  881. */
  882. public function getNextNumRef($soc)
  883. {
  884. global $db, $langs, $conf;
  885. $langs->load("orders");
  886. if (!empty($conf->global->COMMANDE_SUPPLIER_ADDON_NUMBER)) {
  887. $mybool = false;
  888. $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER').'.php';
  889. $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
  890. // Include file with class
  891. $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
  892. foreach ($dirmodels as $reldir) {
  893. $dir = dol_buildpath($reldir."core/modules/supplier_order/");
  894. // Load file with numbering class (if found)
  895. $mybool |= @include_once $dir.$file;
  896. }
  897. if ($mybool === false) {
  898. dol_print_error('', "Failed to include file ".$file);
  899. return '';
  900. }
  901. $obj = new $classname();
  902. $numref = $obj->getNextValue($soc, $this);
  903. if ($numref != "") {
  904. return $numref;
  905. } else {
  906. $this->error = $obj->error;
  907. return -1;
  908. }
  909. } else {
  910. $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
  911. return -2;
  912. }
  913. }
  914. /**
  915. * Class invoiced the supplier order
  916. *
  917. * @param User $user Object user making the change
  918. * @return int <0 if KO, 0 if already billed, >0 if OK
  919. */
  920. public function classifyBilled(User $user)
  921. {
  922. $error = 0;
  923. if ($this->billed) {
  924. return 0;
  925. }
  926. $this->db->begin();
  927. $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_fournisseur SET billed = 1';
  928. $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
  929. if ($this->db->query($sql)) {
  930. if (!$error) {
  931. // Call trigger
  932. $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
  933. if ($result < 0) {
  934. $error++;
  935. }
  936. // End call triggers
  937. }
  938. if (!$error) {
  939. $this->billed = 1;
  940. $this->db->commit();
  941. return 1;
  942. } else {
  943. $this->db->rollback();
  944. return -1;
  945. }
  946. } else {
  947. dol_print_error($this->db);
  948. $this->db->rollback();
  949. return -1;
  950. }
  951. }
  952. /**
  953. * Approve a supplier order
  954. *
  955. * @param User $user Object user
  956. * @param int $idwarehouse Id of warhouse for stock change
  957. * @param int $secondlevel 0=Standard approval, 1=Second level approval (used when option SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set)
  958. * @return int <0 if KO, >0 if OK
  959. */
  960. public function approve($user, $idwarehouse = 0, $secondlevel = 0)
  961. {
  962. global $langs, $conf;
  963. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  964. $error = 0;
  965. dol_syslog(get_class($this)."::approve");
  966. if ($user->hasRight("fournisseur", "commande", "approuver")) {
  967. $now = dol_now();
  968. $this->db->begin();
  969. // Definition of order numbering model name
  970. $soc = new Societe($this->db);
  971. $soc->fetch($this->fourn_id);
  972. // Check if object has a temporary ref
  973. if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
  974. $num = $this->getNextNumRef($soc);
  975. } else {
  976. $num = $this->ref;
  977. }
  978. $this->newref = dol_sanitizeFileName($num);
  979. // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
  980. $movetoapprovestatus = true;
  981. $comment = '';
  982. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
  983. $sql .= " SET ref='".$this->db->escape($num)."',";
  984. if (empty($secondlevel)) { // standard or first level approval
  985. $sql .= " date_approve='".$this->db->idate($now)."',";
  986. $sql .= " fk_user_approve = ".$user->id;
  987. if (!empty($conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) && $this->total_ht >= $conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) {
  988. if (empty($this->user_approve_id2)) {
  989. $movetoapprovestatus = false; // second level approval not done
  990. $comment = ' (first level)';
  991. }
  992. }
  993. } else // request a second level approval
  994. {
  995. $sql .= " date_approve2='".$this->db->idate($now)."',";
  996. $sql .= " fk_user_approve2 = ".((int) $user->id);
  997. if (empty($this->user_approve_id)) {
  998. $movetoapprovestatus = false; // first level approval not done
  999. }
  1000. $comment = ' (second level)';
  1001. }
  1002. // If double approval is required and first approval, we keep status to 1 = validated
  1003. if ($movetoapprovestatus) {
  1004. $sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
  1005. } else {
  1006. $sql .= ", fk_statut = ".self::STATUS_VALIDATED;
  1007. }
  1008. $sql .= " WHERE rowid = ".((int) $this->id);
  1009. $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
  1010. if ($this->db->query($sql)) {
  1011. if (!empty($conf->global->SUPPLIER_ORDER_AUTOADD_USER_CONTACT)) {
  1012. $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
  1013. if ($result < 0 && $result != -2) { // -2 means already exists
  1014. $error++;
  1015. }
  1016. }
  1017. // If stock is incremented on validate order, we must increment it
  1018. if (!$error && $movetoapprovestatus && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
  1019. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
  1020. $langs->load("agenda");
  1021. $cpt = count($this->lines);
  1022. for ($i = 0; $i < $cpt; $i++) {
  1023. // Product with reference
  1024. if ($this->lines[$i]->fk_product > 0) {
  1025. $this->line = $this->lines[$i];
  1026. $mouvP = new MouvementStock($this->db);
  1027. $mouvP->origin = &$this;
  1028. $mouvP->setOrigin($this->element, $this->id);
  1029. // We decrement stock of product (and sub-products)
  1030. $up_ht_disc = $this->lines[$i]->subprice;
  1031. if (!empty($this->lines[$i]->remise_percent) && empty($conf->global->STOCK_EXCLUDE_DISCOUNT_FOR_PMP)) {
  1032. $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
  1033. }
  1034. $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
  1035. if ($result < 0) {
  1036. $error++;
  1037. }
  1038. unset($this->line);
  1039. }
  1040. }
  1041. }
  1042. if (!$error) {
  1043. // Call trigger
  1044. $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
  1045. if ($result < 0) {
  1046. $error++;
  1047. }
  1048. // End call triggers
  1049. }
  1050. if (!$error) {
  1051. $this->ref = $this->newref;
  1052. if ($movetoapprovestatus) {
  1053. $this->statut = self::STATUS_ACCEPTED;
  1054. } else {
  1055. $this->statut = self::STATUS_VALIDATED;
  1056. }
  1057. if (empty($secondlevel)) { // standard or first level approval
  1058. $this->date_approve = $now;
  1059. $this->user_approve_id = $user->id;
  1060. } else // request a second level approval
  1061. {
  1062. $this->date_approve2 = $now;
  1063. $this->user_approve_id2 = $user->id;
  1064. }
  1065. $this->db->commit();
  1066. return 1;
  1067. } else {
  1068. $this->db->rollback();
  1069. return -1;
  1070. }
  1071. } else {
  1072. $this->db->rollback();
  1073. $this->error = $this->db->lasterror();
  1074. return -1;
  1075. }
  1076. } else {
  1077. dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
  1078. }
  1079. return -1;
  1080. }
  1081. /**
  1082. * Refuse an order
  1083. *
  1084. * @param User $user User making action
  1085. * @return int 0 if Ok, <0 if Ko
  1086. */
  1087. public function refuse($user)
  1088. {
  1089. global $conf, $langs;
  1090. $error = 0;
  1091. dol_syslog(get_class($this)."::refuse");
  1092. $result = 0;
  1093. if ($user->hasRight("fournisseur", "commande", "approuver")) {
  1094. $this->db->begin();
  1095. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
  1096. $sql .= " WHERE rowid = ".((int) $this->id);
  1097. if ($this->db->query($sql)) {
  1098. $result = 0;
  1099. if ($error == 0) {
  1100. // Call trigger
  1101. $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
  1102. if ($result < 0) {
  1103. $error++;
  1104. $this->db->rollback();
  1105. } else {
  1106. $this->db->commit();
  1107. }
  1108. // End call triggers
  1109. }
  1110. } else {
  1111. $this->db->rollback();
  1112. $this->error = $this->db->lasterror();
  1113. dol_syslog(get_class($this)."::refuse Error -1");
  1114. $result = -1;
  1115. }
  1116. } else {
  1117. dol_syslog(get_class($this)."::refuse Not Authorized");
  1118. }
  1119. return $result;
  1120. }
  1121. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1122. /**
  1123. * Cancel an approved order.
  1124. * The cancellation is done after approval
  1125. *
  1126. * @param User $user User making action
  1127. * @param int $idwarehouse Id warehouse to use for stock change (not used for supplier orders).
  1128. * @return int >0 if Ok, <0 if Ko
  1129. */
  1130. public function Cancel($user, $idwarehouse = -1)
  1131. {
  1132. // phpcs:enable
  1133. global $langs, $conf;
  1134. $error = 0;
  1135. //dol_syslog("CommandeFournisseur::Cancel");
  1136. $result = 0;
  1137. if ($user->hasRight("fournisseur", "commande", "commander")) {
  1138. $statut = self::STATUS_CANCELED;
  1139. $this->db->begin();
  1140. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur SET fk_statut = ".((int) $statut);
  1141. $sql .= " WHERE rowid = ".((int) $this->id);
  1142. dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
  1143. if ($this->db->query($sql)) {
  1144. $result = 0;
  1145. // Call trigger
  1146. $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
  1147. if ($result < 0) {
  1148. $error++;
  1149. }
  1150. // End call triggers
  1151. if ($error == 0) {
  1152. $this->db->commit();
  1153. return 1;
  1154. } else {
  1155. $this->db->rollback();
  1156. return -1;
  1157. }
  1158. } else {
  1159. $this->db->rollback();
  1160. $this->error = $this->db->lasterror();
  1161. dol_syslog(get_class($this)."::cancel ".$this->error);
  1162. return -1;
  1163. }
  1164. } else {
  1165. dol_syslog(get_class($this)."::cancel Not Authorized");
  1166. return -1;
  1167. }
  1168. }
  1169. /**
  1170. * Submit a supplier order to supplier
  1171. *
  1172. * @param User $user User making change
  1173. * @param integer $date Date
  1174. * @param int $methode Method
  1175. * @param string $comment Comment
  1176. * @return int <0 if KO, >0 if OK
  1177. */
  1178. public function commande($user, $date, $methode, $comment = '')
  1179. {
  1180. global $langs;
  1181. dol_syslog(get_class($this)."::commande");
  1182. $error = 0;
  1183. if ($user->hasRight("fournisseur", "commande", "commander")) {
  1184. $this->db->begin();
  1185. $newnoteprivate = $this->note_private;
  1186. if ($comment) {
  1187. $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
  1188. }
  1189. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
  1190. $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".$methode.", date_commande='".$this->db->idate($date)."', ";
  1191. $sql .= " note_private='".$this->db->escape($newnoteprivate)."'";
  1192. $sql .= " WHERE rowid=".((int) $this->id);
  1193. dol_syslog(get_class($this)."::commande", LOG_DEBUG);
  1194. if ($this->db->query($sql)) {
  1195. $this->statut = self::STATUS_ORDERSENT;
  1196. $this->methode_commande_id = $methode;
  1197. $this->date_commande = $date;
  1198. $this->context = array('comments' => $comment);
  1199. // Call trigger
  1200. $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
  1201. if ($result < 0) {
  1202. $error++;
  1203. }
  1204. // End call triggers
  1205. } else {
  1206. $error++;
  1207. $this->error = $this->db->lasterror();
  1208. $this->errors[] = $this->db->lasterror();
  1209. }
  1210. if (!$error) {
  1211. $this->db->commit();
  1212. } else {
  1213. $this->db->rollback();
  1214. }
  1215. } else {
  1216. $error++;
  1217. $this->error = $langs->trans('NotAuthorized');
  1218. $this->errors[] = $langs->trans('NotAuthorized');
  1219. dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
  1220. }
  1221. return ($error ? -1 : 1);
  1222. }
  1223. /**
  1224. * Create order with draft status
  1225. *
  1226. * @param User $user User making creation
  1227. * @param int $notrigger Disable all triggers
  1228. * @return int <0 if KO, Id of supplier order if OK
  1229. */
  1230. public function create($user, $notrigger = 0)
  1231. {
  1232. global $langs, $conf, $hookmanager;
  1233. $this->db->begin();
  1234. $error = 0;
  1235. $now = dol_now();
  1236. // set tmp vars
  1237. $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
  1238. if (empty($date)) {
  1239. $date = $now;
  1240. }
  1241. $delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
  1242. // Clean parameters
  1243. if (empty($this->source)) {
  1244. $this->source = 0;
  1245. }
  1246. // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
  1247. if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
  1248. list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
  1249. } else {
  1250. $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
  1251. }
  1252. if (empty($this->fk_multicurrency)) {
  1253. $this->multicurrency_code = $conf->currency;
  1254. $this->fk_multicurrency = 0;
  1255. $this->multicurrency_tx = 1;
  1256. }
  1257. // We set order into draft status
  1258. $this->brouillon = 1;
  1259. $sql = "INSERT INTO ".MAIN_DB_PREFIX."commande_fournisseur (";
  1260. $sql .= "ref";
  1261. $sql .= ", ref_supplier";
  1262. $sql .= ", note_private";
  1263. $sql .= ", note_public";
  1264. $sql .= ", entity";
  1265. $sql .= ", fk_soc";
  1266. $sql .= ", fk_projet";
  1267. $sql .= ", date_creation";
  1268. $sql .= ", date_livraison";
  1269. $sql .= ", fk_user_author";
  1270. $sql .= ", fk_statut";
  1271. $sql .= ", source";
  1272. $sql .= ", model_pdf";
  1273. $sql .= ", fk_mode_reglement";
  1274. $sql .= ", fk_cond_reglement";
  1275. $sql .= ", fk_account";
  1276. $sql .= ", fk_incoterms, location_incoterms";
  1277. $sql .= ", fk_multicurrency";
  1278. $sql .= ", multicurrency_code";
  1279. $sql .= ", multicurrency_tx";
  1280. $sql .= ") ";
  1281. $sql .= " VALUES (";
  1282. $sql .= "'(PROV)'";
  1283. $sql .= ", '".$this->db->escape($this->ref_supplier)."'";
  1284. $sql .= ", '".$this->db->escape($this->note_private)."'";
  1285. $sql .= ", '".$this->db->escape($this->note_public)."'";
  1286. $sql .= ", ".setEntity($this);
  1287. $sql .= ", ".((int) $this->socid);
  1288. $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
  1289. $sql .= ", '".$this->db->idate($date)."'";
  1290. $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
  1291. $sql .= ", ".((int) $user->id);
  1292. $sql .= ", ".self::STATUS_DRAFT;
  1293. $sql .= ", ".((int) $this->source);
  1294. $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'";
  1295. $sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
  1296. $sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
  1297. $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
  1298. $sql .= ", ".(int) $this->fk_incoterms;
  1299. $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
  1300. $sql .= ", ".(int) $this->fk_multicurrency;
  1301. $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
  1302. $sql .= ", ".(double) $this->multicurrency_tx;
  1303. $sql .= ")";
  1304. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  1305. if ($this->db->query($sql)) {
  1306. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."commande_fournisseur");
  1307. if ($this->id) {
  1308. $num = count($this->lines);
  1309. // insert products details into database
  1310. for ($i = 0; $i < $num; $i++) {
  1311. $line = $this->lines[$i];
  1312. if (!is_object($line)) {
  1313. $line = (object) $line;
  1314. }
  1315. //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
  1316. // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
  1317. $result = $this->addline(
  1318. $line->desc,
  1319. $line->subprice,
  1320. $line->qty,
  1321. $line->tva_tx,
  1322. $line->localtax1_tx,
  1323. $line->localtax2_tx,
  1324. $line->fk_product,
  1325. 0,
  1326. $line->ref_fourn, // $line->ref_fourn comes from field ref into table of lines. Value may ba a ref that does not exists anymore, so we first try with value of product
  1327. $line->remise_percent,
  1328. 'HT',
  1329. 0,
  1330. $line->product_type,
  1331. $line->info_bits,
  1332. false,
  1333. $line->date_start,
  1334. $line->date_end,
  1335. $line->array_options,
  1336. $line->fk_unit,
  1337. $line->special_code
  1338. );
  1339. if ($result < 0) {
  1340. dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functionnal error
  1341. $this->db->rollback();
  1342. return -1;
  1343. }
  1344. }
  1345. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
  1346. $sql .= " SET ref='(PROV".$this->id.")'";
  1347. $sql .= " WHERE rowid=".((int) $this->id);
  1348. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  1349. if ($this->db->query($sql)) {
  1350. // Add link with price request and supplier order
  1351. if ($this->id) {
  1352. $this->ref = "(PROV".$this->id.")";
  1353. if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
  1354. $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
  1355. }
  1356. // Add object linked
  1357. if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
  1358. foreach ($this->linked_objects as $origin => $tmp_origin_id) {
  1359. if (is_array($tmp_origin_id)) { // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...))
  1360. foreach ($tmp_origin_id as $origin_id) {
  1361. $ret = $this->add_object_linked($origin, $origin_id);
  1362. if (!$ret) {
  1363. dol_print_error($this->db);
  1364. $error++;
  1365. }
  1366. }
  1367. } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
  1368. {
  1369. $origin_id = $tmp_origin_id;
  1370. $ret = $this->add_object_linked($origin, $origin_id);
  1371. if (!$ret) {
  1372. dol_print_error($this->db);
  1373. $error++;
  1374. }
  1375. }
  1376. }
  1377. }
  1378. }
  1379. if (!$error) {
  1380. $result = $this->insertExtraFields();
  1381. if ($result < 0) {
  1382. $error++;
  1383. }
  1384. }
  1385. if (!$error && !$notrigger) {
  1386. // Call trigger
  1387. $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
  1388. if ($result < 0) {
  1389. $this->db->rollback();
  1390. return -1;
  1391. }
  1392. // End call triggers
  1393. }
  1394. $this->db->commit();
  1395. return $this->id;
  1396. } else {
  1397. $this->error = $this->db->lasterror();
  1398. $this->db->rollback();
  1399. return -2;
  1400. }
  1401. } else {
  1402. $this->error = 'Failed to get ID of inserted line';
  1403. return -1;
  1404. }
  1405. } else {
  1406. $this->error = $this->db->lasterror();
  1407. $this->db->rollback();
  1408. return -1;
  1409. }
  1410. }
  1411. /**
  1412. * Update Supplier Order
  1413. *
  1414. * @param User $user User that modify
  1415. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  1416. * @return int <0 if KO, >0 if OK
  1417. */
  1418. public function update(User $user, $notrigger = 0)
  1419. {
  1420. global $conf;
  1421. $error = 0;
  1422. // Clean parameters
  1423. if (isset($this->ref)) {
  1424. $this->ref = trim($this->ref);
  1425. }
  1426. if (isset($this->ref_supplier)) {
  1427. $this->ref_supplier = trim($this->ref_supplier);
  1428. }
  1429. if (isset($this->note_private)) {
  1430. $this->note_private = trim($this->note_private);
  1431. }
  1432. if (isset($this->note_public)) {
  1433. $this->note_public = trim($this->note_public);
  1434. }
  1435. if (isset($this->model_pdf)) {
  1436. $this->model_pdf = trim($this->model_pdf);
  1437. }
  1438. if (isset($this->import_key)) {
  1439. $this->import_key = trim($this->import_key);
  1440. }
  1441. // Update request
  1442. $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
  1443. $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
  1444. $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
  1445. $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
  1446. $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
  1447. $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
  1448. $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
  1449. $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
  1450. $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
  1451. $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
  1452. $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
  1453. $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
  1454. $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
  1455. $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
  1456. $sql .= " fk_user_valid=".(isset($this->user_valid) && $this->user_valid > 0 ? $this->user_valid : "null").",";
  1457. $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
  1458. $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
  1459. $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
  1460. $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
  1461. //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
  1462. $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
  1463. //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
  1464. $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
  1465. $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
  1466. $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
  1467. $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
  1468. $sql .= " WHERE rowid=".((int) $this->id);
  1469. $this->db->begin();
  1470. dol_syslog(get_class($this)."::update", LOG_DEBUG);
  1471. $resql = $this->db->query($sql);
  1472. if (!$resql) {
  1473. $error++;
  1474. $this->errors[] = "Error ".$this->db->lasterror();
  1475. }
  1476. if (!$error) {
  1477. $result = $this->insertExtraFields();
  1478. if ($result < 0) {
  1479. $error++;
  1480. }
  1481. }
  1482. if (!$error && !$notrigger) {
  1483. // Call trigger
  1484. $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
  1485. if ($result < 0) {
  1486. $error++;
  1487. }
  1488. // End call triggers
  1489. }
  1490. // Commit or rollback
  1491. if ($error) {
  1492. foreach ($this->errors as $errmsg) {
  1493. dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
  1494. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  1495. }
  1496. $this->db->rollback();
  1497. return -1 * $error;
  1498. } else {
  1499. $this->db->commit();
  1500. return 1;
  1501. }
  1502. }
  1503. /**
  1504. * Load an object from its id and create a new one in database
  1505. *
  1506. * @param User $user User making the clone
  1507. * @param int $socid Id of thirdparty
  1508. * @param int $notrigger Disable all triggers
  1509. * @return int New id of clone
  1510. */
  1511. public function createFromClone(User $user, $socid = 0, $notrigger = 0)
  1512. {
  1513. global $conf, $user, $hookmanager;
  1514. $error = 0;
  1515. $this->db->begin();
  1516. // get extrafields so they will be clone
  1517. foreach ($this->lines as $line) {
  1518. $line->fetch_optionals();
  1519. }
  1520. // Load source object
  1521. $objFrom = clone $this;
  1522. // Change socid if needed
  1523. if (!empty($socid) && $socid != $this->socid) {
  1524. $objsoc = new Societe($this->db);
  1525. if ($objsoc->fetch($socid) > 0) {
  1526. $this->socid = $objsoc->id;
  1527. $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
  1528. $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
  1529. $this->fk_project = 0;
  1530. $this->fk_delivery_address = 0;
  1531. }
  1532. // TODO Change product price if multi-prices
  1533. }
  1534. $this->id = 0;
  1535. $this->statut = self::STATUS_DRAFT;
  1536. // Clear fields
  1537. $this->user_author_id = $user->id;
  1538. $this->user_valid = 0;
  1539. $this->date_creation = '';
  1540. $this->date_validation = '';
  1541. $this->ref_supplier = '';
  1542. $this->user_approve_id = '';
  1543. $this->user_approve_id2 = '';
  1544. $this->date_approve = '';
  1545. $this->date_approve2 = '';
  1546. // Create clone
  1547. $this->context['createfromclone'] = 'createfromclone';
  1548. $result = $this->create($user, $notrigger);
  1549. if ($result < 0) {
  1550. $error++;
  1551. }
  1552. if (!$error) {
  1553. // Hook of thirdparty module
  1554. if (is_object($hookmanager)) {
  1555. $parameters = array('objFrom'=>$objFrom);
  1556. $action = '';
  1557. $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  1558. if ($reshook < 0) {
  1559. $this->setErrorsFromObject($hookmanager);
  1560. $error++;
  1561. }
  1562. }
  1563. }
  1564. unset($this->context['createfromclone']);
  1565. // End
  1566. if (!$error) {
  1567. $this->db->commit();
  1568. return $this->id;
  1569. } else {
  1570. $this->db->rollback();
  1571. return -1;
  1572. }
  1573. }
  1574. /**
  1575. * Add order line
  1576. *
  1577. * @param string $desc Description
  1578. * @param float $pu_ht Unit price (used if $price_base_type is 'HT')
  1579. * @param float $qty Quantity
  1580. * @param float $txtva Taux tva
  1581. * @param float $txlocaltax1 Localtax1 tax
  1582. * @param float $txlocaltax2 Localtax2 tax
  1583. * @param int $fk_product Id product
  1584. * @param int $fk_prod_fourn_price Id supplier price
  1585. * @param string $ref_supplier Supplier reference price
  1586. * @param float $remise_percent Remise
  1587. * @param string $price_base_type HT or TTC
  1588. * @param float $pu_ttc Unit price TTC (used if $price_base_type is 'TTC')
  1589. * @param int $type Type of line (0=product, 1=service)
  1590. * @param int $info_bits More information
  1591. * @param bool $notrigger Disable triggers
  1592. * @param int $date_start Date start of service
  1593. * @param int $date_end Date end of service
  1594. * @param array $array_options extrafields array
  1595. * @param string $fk_unit Code of the unit to use. Null to use the default one
  1596. * @param string $pu_ht_devise Amount in currency
  1597. * @param string $origin 'order', ...
  1598. * @param int $origin_id Id of origin object
  1599. * @param int $rang Rank
  1600. * @param int $special_code Special code
  1601. * @return int <=0 if KO, >0 if OK
  1602. */
  1603. public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $fk_prod_fourn_price = 0, $ref_supplier = '', $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $type = 0, $info_bits = 0, $notrigger = false, $date_start = null, $date_end = null, $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $origin = '', $origin_id = 0, $rang = -1, $special_code = 0)
  1604. {
  1605. global $langs, $mysoc, $conf;
  1606. dol_syslog(get_class($this)."::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $fk_prod_fourn_price, $ref_supplier, $remise_percent, $price_base_type, $pu_ttc, $type, $info_bits, $notrigger, $date_start, $date_end, $fk_unit, $pu_ht_devise, $origin, $origin_id");
  1607. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  1608. if ($this->statut == self::STATUS_DRAFT) {
  1609. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  1610. // Clean parameters
  1611. if (empty($qty)) {
  1612. $qty = 0;
  1613. }
  1614. if (!$info_bits) {
  1615. $info_bits = 0;
  1616. }
  1617. if (empty($txtva)) {
  1618. $txtva = 0;
  1619. }
  1620. if (empty($rang)) {
  1621. $rang = 0;
  1622. }
  1623. if (empty($txlocaltax1)) {
  1624. $txlocaltax1 = 0;
  1625. }
  1626. if (empty($txlocaltax2)) {
  1627. $txlocaltax2 = 0;
  1628. }
  1629. if (empty($remise_percent)) {
  1630. $remise_percent = 0;
  1631. }
  1632. $remise_percent = price2num($remise_percent);
  1633. $qty = price2num($qty);
  1634. $pu_ht = price2num($pu_ht);
  1635. $pu_ht_devise = price2num($pu_ht_devise);
  1636. $pu_ttc = price2num($pu_ttc);
  1637. if (!preg_match('/\((.*)\)/', $txtva)) {
  1638. $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
  1639. }
  1640. $txlocaltax1 = price2num($txlocaltax1);
  1641. $txlocaltax2 = price2num($txlocaltax2);
  1642. if ($price_base_type == 'HT') {
  1643. $pu = $pu_ht;
  1644. } else {
  1645. $pu = $pu_ttc;
  1646. }
  1647. $desc = trim($desc);
  1648. // Check parameters
  1649. if ($qty < 0 && !$fk_product) {
  1650. $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
  1651. return -1;
  1652. }
  1653. if ($type < 0) {
  1654. return -1;
  1655. }
  1656. if ($date_start && $date_end && $date_start > $date_end) {
  1657. $langs->load("errors");
  1658. $this->error = $langs->trans('ErrorStartDateGreaterEnd');
  1659. return -1;
  1660. }
  1661. $this->db->begin();
  1662. $product_type = $type;
  1663. $label = ''; // deprecated
  1664. if ($fk_product > 0) {
  1665. if (!empty($conf->global->SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY)) { // Not the common case
  1666. // Check quantity is enough
  1667. dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." fk_prod_fourn_price=".$fk_prod_fourn_price." qty=".$qty." ref_supplier=".$ref_supplier);
  1668. $prod = new ProductFournisseur($this->db);
  1669. if ($prod->fetch($fk_product) > 0) {
  1670. $product_type = $prod->type;
  1671. $label = $prod->label;
  1672. // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
  1673. // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
  1674. $result = $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (isset($this->fk_soc) ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
  1675. // If supplier order created from sales order, we take best supplier price
  1676. // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
  1677. if ($result > 0 && ($origin == 'commande' || $pu === '')) {
  1678. $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
  1679. $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
  1680. // is remise percent not keyed but present for the product we add it
  1681. if ($remise_percent == 0 && $prod->remise_percent != 0) {
  1682. $remise_percent = $prod->remise_percent;
  1683. }
  1684. }
  1685. if ($result == 0) { // If result == 0, we failed to found the supplier reference price
  1686. $langs->load("errors");
  1687. $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
  1688. $this->db->rollback();
  1689. dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
  1690. //$pu = $prod->fourn_pu; // We do not overwrite unit price
  1691. //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
  1692. return -1;
  1693. }
  1694. if ($result == -1) {
  1695. $langs->load("errors");
  1696. $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
  1697. $this->db->rollback();
  1698. dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
  1699. return -1;
  1700. }
  1701. if ($result < -1) {
  1702. $this->error = $prod->error;
  1703. $this->db->rollback();
  1704. dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
  1705. return -1;
  1706. }
  1707. } else {
  1708. $this->error = $prod->error;
  1709. $this->db->rollback();
  1710. return -1;
  1711. }
  1712. }
  1713. // Predefine quantity according to packaging
  1714. if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
  1715. $prod = new Product($this->db);
  1716. $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', ($this->fk_soc ? $this->fk_soc : $this->socid));
  1717. if ($qty < $prod->packaging) {
  1718. $qty = $prod->packaging;
  1719. } else {
  1720. if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) {
  1721. $coeff = intval($qty / $prod->packaging) + 1;
  1722. $qty = $prod->packaging * $coeff;
  1723. setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
  1724. }
  1725. }
  1726. }
  1727. }
  1728. if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
  1729. $pu = 0;
  1730. }
  1731. $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
  1732. // Clean vat code
  1733. $reg = array();
  1734. $vat_src_code = '';
  1735. if (preg_match('/\((.*)\)/', $txtva, $reg)) {
  1736. $vat_src_code = $reg[1];
  1737. $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
  1738. }
  1739. // Calcul du total TTC et de la TVA pour la ligne a partir de
  1740. // qty, pu, remise_percent et txtva
  1741. // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
  1742. // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
  1743. $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
  1744. $total_ht = $tabprice[0];
  1745. $total_tva = $tabprice[1];
  1746. $total_ttc = $tabprice[2];
  1747. $total_localtax1 = $tabprice[9];
  1748. $total_localtax2 = $tabprice[10];
  1749. $pu = $pu_ht = $tabprice[3];
  1750. // MultiCurrency
  1751. $multicurrency_total_ht = $tabprice[16];
  1752. $multicurrency_total_tva = $tabprice[17];
  1753. $multicurrency_total_ttc = $tabprice[18];
  1754. $pu_ht_devise = $tabprice[19];
  1755. $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
  1756. $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
  1757. if ($rang < 0) {
  1758. $rangmax = $this->line_max();
  1759. $rang = $rangmax + 1;
  1760. }
  1761. // Insert line
  1762. $this->line = new CommandeFournisseurLigne($this->db);
  1763. $this->line->context = $this->context;
  1764. $this->line->fk_commande = $this->id;
  1765. $this->line->label = $label;
  1766. $this->line->ref_fourn = $ref_supplier;
  1767. $this->line->ref_supplier = $ref_supplier;
  1768. $this->line->desc = $desc;
  1769. $this->line->qty = $qty;
  1770. $this->line->tva_tx = $txtva;
  1771. $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
  1772. $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
  1773. $this->line->localtax1_type = $localtax1_type;
  1774. $this->line->localtax2_type = $localtax2_type;
  1775. $this->line->fk_product = $fk_product;
  1776. $this->line->product_type = $product_type;
  1777. $this->line->remise_percent = $remise_percent;
  1778. $this->line->subprice = $pu_ht;
  1779. $this->line->rang = $rang;
  1780. $this->line->info_bits = $info_bits;
  1781. $this->line->vat_src_code = $vat_src_code;
  1782. $this->line->total_ht = $total_ht;
  1783. $this->line->total_tva = $total_tva;
  1784. $this->line->total_localtax1 = $total_localtax1;
  1785. $this->line->total_localtax2 = $total_localtax2;
  1786. $this->line->total_ttc = $total_ttc;
  1787. $this->line->product_type = $type;
  1788. $this->line->special_code = (!empty($this->special_code) ? $this->special_code : 0);
  1789. $this->line->origin = $origin;
  1790. $this->line->origin_id = $origin_id;
  1791. $this->line->fk_unit = $fk_unit;
  1792. $this->line->date_start = $date_start;
  1793. $this->line->date_end = $date_end;
  1794. // Multicurrency
  1795. $this->line->fk_multicurrency = $this->fk_multicurrency;
  1796. $this->line->multicurrency_code = $this->multicurrency_code;
  1797. $this->line->multicurrency_subprice = $pu_ht_devise;
  1798. $this->line->multicurrency_total_ht = $multicurrency_total_ht;
  1799. $this->line->multicurrency_total_tva = $multicurrency_total_tva;
  1800. $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
  1801. $this->line->subprice = $pu_ht;
  1802. $this->line->price = $this->line->subprice;
  1803. $this->line->remise_percent = $remise_percent;
  1804. if (is_array($array_options) && count($array_options) > 0) {
  1805. $this->line->array_options = $array_options;
  1806. }
  1807. $result = $this->line->insert($notrigger);
  1808. if ($result > 0) {
  1809. // Reorder if child line
  1810. if (!empty($fk_parent_line)) {
  1811. $this->line_order(true, 'DESC');
  1812. } elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
  1813. $linecount = count($this->lines);
  1814. for ($ii = $rang; $ii <= $linecount; $ii++) {
  1815. $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
  1816. }
  1817. }
  1818. // Mise a jour informations denormalisees au niveau de la commande meme
  1819. $result = $this->update_price(1, 'auto', 0, $this->thirdparty); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
  1820. if ($result > 0) {
  1821. $this->db->commit();
  1822. return $this->line->id;
  1823. } else {
  1824. $this->db->rollback();
  1825. return -1;
  1826. }
  1827. } else {
  1828. $this->error = $this->line->error;
  1829. $this->errors = $this->line->errors;
  1830. dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
  1831. $this->db->rollback();
  1832. return -1;
  1833. }
  1834. }
  1835. return -1;
  1836. }
  1837. /**
  1838. * Save a receiving into the tracking table of receiving (commande_fournisseur_dispatch) and add product into stock warehouse.
  1839. *
  1840. * @param User $user User object making change
  1841. * @param int $product Id of product to dispatch
  1842. * @param double $qty Qty to dispatch
  1843. * @param int $entrepot Id of warehouse to add product
  1844. * @param double $price Unit Price for PMP value calculation (Unit price without Tax and taking into account discount)
  1845. * @param string $comment Comment for stock movement
  1846. * @param integer $eatby eat-by date
  1847. * @param integer $sellby sell-by date
  1848. * @param string $batch Lot number
  1849. * @param int $fk_commandefourndet Id of supplier order line
  1850. * @param int $notrigger 1 = notrigger
  1851. * @param int $fk_reception Id of reception to link
  1852. * @return int <0 if KO, >0 if OK
  1853. */
  1854. public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
  1855. {
  1856. global $conf, $langs;
  1857. $error = 0;
  1858. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
  1859. // Check parameters (if test are wrong here, there is bug into caller)
  1860. if ($entrepot <= 0) {
  1861. $this->error = 'ErrorBadValueForParameterWarehouse';
  1862. return -1;
  1863. }
  1864. if ($qty == 0) {
  1865. $this->error = 'ErrorBadValueForParameterQty';
  1866. return -1;
  1867. }
  1868. $dispatchstatus = 1;
  1869. if (!empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
  1870. $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
  1871. }
  1872. $now = dol_now();
  1873. $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
  1874. if (($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY || $this->statut == self::STATUS_RECEIVED_COMPLETELY)) {
  1875. $this->db->begin();
  1876. $sql = "INSERT INTO ".MAIN_DB_PREFIX."commande_fournisseur_dispatch";
  1877. $sql .= " (fk_commande, fk_product, qty, fk_entrepot, fk_user, datec, fk_commandefourndet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
  1878. $sql .= " ('".$this->id."','".$product."','".$qty."',".($entrepot > 0 ? "'".$entrepot."'" : "null").",'".$user->id."','".$this->db->idate($now)."','".$fk_commandefourndet."', ".$dispatchstatus.", '".$this->db->escape($comment)."', ";
  1879. $sql .= ($eatby ? "'".$this->db->idate($eatby)."'" : "null").", ".($sellby ? "'".$this->db->idate($sellby)."'" : "null").", ".($batch ? "'".$this->db->escape($batch)."'" : "null").", ".($fk_reception > 0 ? "'".$this->db->escape($fk_reception)."'" : "null");
  1880. $sql .= ")";
  1881. dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
  1882. $resql = $this->db->query($sql);
  1883. if ($resql) {
  1884. if (!$notrigger) {
  1885. global $conf, $langs, $user;
  1886. // Call trigger
  1887. $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
  1888. if ($result < 0) {
  1889. $error++;
  1890. }
  1891. // End call triggers
  1892. }
  1893. } else {
  1894. $this->error = $this->db->lasterror();
  1895. $error++;
  1896. }
  1897. // If module stock is enabled and the stock increase is done on purchase order dispatching
  1898. if (!$error && $entrepot > 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
  1899. $mouv = new MouvementStock($this->db);
  1900. if ($product > 0) {
  1901. // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
  1902. $mouv->origin = &$this;
  1903. $mouv->setOrigin($this->element, $this->id);
  1904. // Method change if qty < 0
  1905. if (!empty($conf->global->SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN) && $qty < 0) {
  1906. $result = $mouv->livraison($user, $product, $entrepot, $qty*(-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
  1907. } else {
  1908. $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
  1909. }
  1910. if ($result < 0) {
  1911. $this->error = $mouv->error;
  1912. $this->errors = $mouv->errors;
  1913. dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".join(',', $this->errors), LOG_ERR);
  1914. $error++;
  1915. }
  1916. }
  1917. }
  1918. if ($error == 0) {
  1919. $this->db->commit();
  1920. return 1;
  1921. } else {
  1922. $this->db->rollback();
  1923. return -1;
  1924. }
  1925. } else {
  1926. $this->error = 'BadStatusForObject';
  1927. return -2;
  1928. }
  1929. }
  1930. /**
  1931. * Delete line
  1932. *
  1933. * @param int $idline Id of line to delete
  1934. * @param int $notrigger 1=Disable call to triggers
  1935. * @return int <0 if KO, >0 if OK
  1936. */
  1937. public function deleteline($idline, $notrigger = 0)
  1938. {
  1939. if ($this->statut == 0) {
  1940. $line = new CommandeFournisseurLigne($this->db);
  1941. if ($line->fetch($idline) <= 0) {
  1942. return 0;
  1943. }
  1944. // check if not yet received
  1945. $dispatchedLines = $this->getDispachedLines();
  1946. foreach ($dispatchedLines as $dispatchLine) {
  1947. if ($dispatchLine['orderlineid'] == $idline) {
  1948. $this->error = "LineAlreadyDispatched";
  1949. $this->errors[] = $this->error;
  1950. return -3;
  1951. }
  1952. }
  1953. if ($line->delete($notrigger) > 0) {
  1954. $this->update_price(1);
  1955. return 1;
  1956. } else {
  1957. $this->error = $line->error;
  1958. $this->errors = $line->errors;
  1959. return -1;
  1960. }
  1961. } else {
  1962. return -2;
  1963. }
  1964. }
  1965. /**
  1966. * Delete an order
  1967. *
  1968. * @param User $user Object user
  1969. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  1970. * @return int <0 if KO, >0 if OK
  1971. */
  1972. public function delete(User $user, $notrigger = 0)
  1973. {
  1974. global $langs, $conf;
  1975. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  1976. $error = 0;
  1977. $this->db->begin();
  1978. if (empty($notrigger)) {
  1979. // Call trigger
  1980. $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
  1981. if ($result < 0) {
  1982. $this->errors[] = 'ErrorWhenRunningTrigger';
  1983. dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
  1984. $this->db->rollback();
  1985. return -1;
  1986. }
  1987. // End call triggers
  1988. }
  1989. // Test we can delete
  1990. $this->fetchObjectLinked(null, 'order_supplier');
  1991. if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
  1992. foreach ($this->linkedObjects['reception'] as $element) {
  1993. if ($element->statut >= 0) {
  1994. $this->errors[] = $langs->trans('ReceptionExist');
  1995. $error++;
  1996. break;
  1997. }
  1998. }
  1999. }
  2000. $main = MAIN_DB_PREFIX.'commande_fournisseurdet';
  2001. $ef = $main."_extrafields";
  2002. $sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_commande = ".((int) $this->id).")";
  2003. dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
  2004. if (!$this->db->query($sql)) {
  2005. $this->error = $this->db->lasterror();
  2006. $this->errors[] = $this->db->lasterror();
  2007. $error++;
  2008. }
  2009. $sql = "DELETE FROM ".MAIN_DB_PREFIX."commande_fournisseurdet WHERE fk_commande =".((int) $this->id);
  2010. dol_syslog(get_class($this)."::delete", LOG_DEBUG);
  2011. if (!$this->db->query($sql)) {
  2012. $this->error = $this->db->lasterror();
  2013. $this->errors[] = $this->db->lasterror();
  2014. $error++;
  2015. }
  2016. $sql = "DELETE FROM ".MAIN_DB_PREFIX."commande_fournisseur WHERE rowid =".((int) $this->id);
  2017. dol_syslog(get_class($this)."::delete", LOG_DEBUG);
  2018. if ($resql = $this->db->query($sql)) {
  2019. if ($this->db->affected_rows($resql) < 1) {
  2020. $this->error = $this->db->lasterror();
  2021. $this->errors[] = $this->db->lasterror();
  2022. $error++;
  2023. }
  2024. } else {
  2025. $this->error = $this->db->lasterror();
  2026. $this->errors[] = $this->db->lasterror();
  2027. $error++;
  2028. }
  2029. // Remove extrafields
  2030. if (!$error) {
  2031. $result = $this->deleteExtraFields();
  2032. if ($result < 0) {
  2033. $this->error = 'FailToDeleteExtraFields';
  2034. $this->errors[] = 'FailToDeleteExtraFields';
  2035. $error++;
  2036. dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
  2037. }
  2038. }
  2039. // Delete linked object
  2040. $res = $this->deleteObjectLinked();
  2041. if ($res < 0) {
  2042. $this->error = 'FailToDeleteObjectLinked';
  2043. $this->errors[] = 'FailToDeleteObjectLinked';
  2044. $error++;
  2045. }
  2046. if (!$error) {
  2047. // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
  2048. $this->deleteEcmFiles();
  2049. // We remove directory
  2050. $ref = dol_sanitizeFileName($this->ref);
  2051. if ($conf->fournisseur->commande->dir_output) {
  2052. $dir = $conf->fournisseur->commande->dir_output."/".$ref;
  2053. $file = $dir."/".$ref.".pdf";
  2054. if (file_exists($file)) {
  2055. if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
  2056. $this->error = 'ErrorFailToDeleteFile';
  2057. $this->errors[] = 'ErrorFailToDeleteFile';
  2058. $error++;
  2059. }
  2060. }
  2061. if (file_exists($dir)) {
  2062. $res = @dol_delete_dir_recursive($dir);
  2063. if (!$res) {
  2064. $this->error = 'ErrorFailToDeleteDir';
  2065. $this->errors[] = 'ErrorFailToDeleteDir';
  2066. $error++;
  2067. }
  2068. }
  2069. }
  2070. }
  2071. if (!$error) {
  2072. dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
  2073. $this->db->commit();
  2074. return 1;
  2075. } else {
  2076. dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
  2077. $this->db->rollback();
  2078. return -$error;
  2079. }
  2080. }
  2081. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2082. /**
  2083. * Get list of order methods
  2084. *
  2085. * @return int 0 if OK, <0 if KO
  2086. */
  2087. public function get_methodes_commande()
  2088. {
  2089. // phpcs:enable
  2090. $sql = "SELECT rowid, libelle";
  2091. $sql .= " FROM ".MAIN_DB_PREFIX."c_input_method";
  2092. $sql .= " WHERE active = 1";
  2093. $resql = $this->db->query($sql);
  2094. if ($resql) {
  2095. $i = 0;
  2096. $num = $this->db->num_rows($resql);
  2097. $this->methodes_commande = array();
  2098. while ($i < $num) {
  2099. $row = $this->db->fetch_row($resql);
  2100. $this->methodes_commande[$row[0]] = $row[1];
  2101. $i++;
  2102. }
  2103. return 0;
  2104. } else {
  2105. return -1;
  2106. }
  2107. }
  2108. /**
  2109. * Return array of dispatched lines waiting to be approved for this order
  2110. *
  2111. * @since 8.0 Return dispatched quantity (qty).
  2112. *
  2113. * @param int $status Filter on stats (-1 = no filter, 0 = lines draft to be approved, 1 = approved lines)
  2114. * @return array Array of lines
  2115. */
  2116. public function getDispachedLines($status = -1)
  2117. {
  2118. $ret = array();
  2119. // List of already dispatched lines
  2120. $sql = "SELECT p.ref, p.label,";
  2121. $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
  2122. $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_commandefourndet";
  2123. $sql .= " FROM ".MAIN_DB_PREFIX."product as p,";
  2124. $sql .= " ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as cfd";
  2125. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entrepot as e ON cfd.fk_entrepot = e.rowid";
  2126. $sql .= " WHERE cfd.fk_commande = ".((int) $this->id);
  2127. $sql .= " AND cfd.fk_product = p.rowid";
  2128. if ($status >= 0) {
  2129. $sql .= " AND cfd.status = ".((int) $status);
  2130. }
  2131. $sql .= " ORDER BY cfd.rowid ASC";
  2132. $resql = $this->db->query($sql);
  2133. if ($resql) {
  2134. $num = $this->db->num_rows($resql);
  2135. $i = 0;
  2136. while ($i < $num) {
  2137. $objp = $this->db->fetch_object($resql);
  2138. if ($objp) {
  2139. $ret[] = array(
  2140. 'id' => $objp->dispatchedlineid,
  2141. 'productid' => $objp->fk_product,
  2142. 'warehouseid' => $objp->warehouse_id,
  2143. 'qty' => $objp->qty,
  2144. 'orderlineid' => $objp->fk_commandefourndet
  2145. );
  2146. }
  2147. $i++;
  2148. }
  2149. } else {
  2150. dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
  2151. }
  2152. return $ret;
  2153. }
  2154. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2155. /**
  2156. * Set a delivery in database for this supplier order
  2157. *
  2158. * @param User $user User that input data
  2159. * @param integer $date Date of reception
  2160. * @param string $type Type of receipt ('tot' = total/done, 'par' = partial, 'nev' = never, 'can' = cancel)
  2161. * @param string $comment Comment
  2162. * @return int <0 if KO, >0 if OK
  2163. */
  2164. public function Livraison($user, $date, $type, $comment)
  2165. {
  2166. // phpcs:enable
  2167. global $conf, $langs;
  2168. $result = 0;
  2169. $error = 0;
  2170. dol_syslog(get_class($this)."::Livraison");
  2171. $usercanreceive = 0;
  2172. if (!isModEnabled('reception')) {
  2173. $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
  2174. } else {
  2175. $usercanreceive = $user->hasRight("reception", "creer");
  2176. }
  2177. if ($usercanreceive) {
  2178. // Define the new status
  2179. if ($type == 'par') {
  2180. $statut = self::STATUS_RECEIVED_PARTIALLY;
  2181. } elseif ($type == 'tot') {
  2182. $statut = self::STATUS_RECEIVED_COMPLETELY;
  2183. } elseif ($type == 'nev') {
  2184. $statut = self::STATUS_CANCELED_AFTER_ORDER;
  2185. } elseif ($type == 'can') {
  2186. $statut = self::STATUS_CANCELED_AFTER_ORDER;
  2187. } else {
  2188. $error++;
  2189. dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
  2190. return -2;
  2191. }
  2192. // Some checks to accept the record
  2193. if (!empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
  2194. // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
  2195. if (!$error && ($type == 'tot')) {
  2196. $dispatchedlinearray = $this->getDispachedLines(0);
  2197. if (count($dispatchedlinearray) > 0) {
  2198. $result = -1;
  2199. $error++;
  2200. $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
  2201. dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
  2202. }
  2203. }
  2204. if (!$error && !empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS_NEED_APPROVE) && ($type == 'tot')) { // Accept to move to reception done, only if status of all line are ok (refuse denied)
  2205. $dispatcheddenied = $this->getDispachedLines(2);
  2206. if (count($dispatchedlinearray) > 0) {
  2207. $result = -1;
  2208. $error++;
  2209. $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
  2210. dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
  2211. }
  2212. }
  2213. }
  2214. // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
  2215. if (empty($error)) {
  2216. $this->db->begin();
  2217. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
  2218. $sql .= " SET fk_statut = ".((int) $statut);
  2219. $sql .= " WHERE rowid = ".((int) $this->id);
  2220. $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
  2221. dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
  2222. $resql = $this->db->query($sql);
  2223. if ($resql) {
  2224. $result = 1;
  2225. $old_statut = $this->statut;
  2226. $this->statut = $statut;
  2227. $this->context['actionmsg2'] = $comment;
  2228. // Call trigger
  2229. $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
  2230. if ($result_trigger < 0) {
  2231. $error++;
  2232. }
  2233. // End call triggers
  2234. if (empty($error)) {
  2235. $this->db->commit();
  2236. } else {
  2237. $this->statut = $old_statut;
  2238. $this->db->rollback();
  2239. $this->error = $this->db->lasterror();
  2240. $result = -1;
  2241. }
  2242. } else {
  2243. $this->db->rollback();
  2244. $this->error = $this->db->lasterror();
  2245. $result = -1;
  2246. }
  2247. }
  2248. } else {
  2249. $this->error = $langs->trans('NotAuthorized');
  2250. $this->errors[] = $langs->trans('NotAuthorized');
  2251. dol_syslog(get_class($this)."::Livraison Not Authorized");
  2252. $result = -3;
  2253. }
  2254. return $result;
  2255. }
  2256. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2257. /**
  2258. * Set delivery date
  2259. *
  2260. * @param User $user Object user that modify
  2261. * @param int $delivery_date Delivery date
  2262. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2263. * @return int <0 if ko, >0 if ok
  2264. * @deprecated Use setDeliveryDate
  2265. */
  2266. public function set_date_livraison($user, $delivery_date, $notrigger = 0)
  2267. {
  2268. // phpcs:enable
  2269. return $this->setDeliveryDate($user, $delivery_date, $notrigger);
  2270. }
  2271. /**
  2272. * Set the planned delivery date
  2273. *
  2274. * @param User $user Objet user making change
  2275. * @param integer $delivery_date Planned delivery date
  2276. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2277. * @return int <0 if KO, >0 if OK
  2278. */
  2279. public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
  2280. {
  2281. if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
  2282. $error = 0;
  2283. $this->db->begin();
  2284. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
  2285. $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
  2286. $sql .= " WHERE rowid = ".((int) $this->id);
  2287. dol_syslog(__METHOD__, LOG_DEBUG);
  2288. $resql = $this->db->query($sql);
  2289. if (!$resql) {
  2290. $this->errors[] = $this->db->error();
  2291. $error++;
  2292. }
  2293. if (!$error) {
  2294. $this->oldcopy = clone $this;
  2295. $this->date_livraison = $delivery_date;
  2296. $this->delivery_date = $delivery_date;
  2297. }
  2298. if (!$notrigger && empty($error)) {
  2299. // Call trigger
  2300. $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
  2301. if ($result < 0) {
  2302. $error++;
  2303. }
  2304. // End call triggers
  2305. }
  2306. if (!$error) {
  2307. $this->db->commit();
  2308. return 1;
  2309. } else {
  2310. foreach ($this->errors as $errmsg) {
  2311. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2312. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2313. }
  2314. $this->db->rollback();
  2315. return -1 * $error;
  2316. }
  2317. } else {
  2318. return -2;
  2319. }
  2320. }
  2321. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2322. /**
  2323. * Set the id projet
  2324. *
  2325. * @param User $user Objet utilisateur qui modifie
  2326. * @param int $id_projet Delivery date
  2327. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2328. * @return int <0 si ko, >0 si ok
  2329. */
  2330. public function set_id_projet($user, $id_projet, $notrigger = 0)
  2331. {
  2332. // phpcs:enable
  2333. if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
  2334. $error = 0;
  2335. $this->db->begin();
  2336. $sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
  2337. $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
  2338. $sql .= " WHERE rowid = ".((int) $this->id);
  2339. dol_syslog(__METHOD__, LOG_DEBUG);
  2340. $resql = $this->db->query($sql);
  2341. if (!$resql) {
  2342. $this->errors[] = $this->db->error();
  2343. $error++;
  2344. }
  2345. if (!$error) {
  2346. $this->oldcopy = clone $this;
  2347. $this->fk_projet = $id_projet;
  2348. $this->fk_project = $id_projet;
  2349. }
  2350. if (!$notrigger && empty($error)) {
  2351. // Call trigger
  2352. $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
  2353. if ($result < 0) {
  2354. $error++;
  2355. }
  2356. // End call triggers
  2357. }
  2358. if (!$error) {
  2359. $this->db->commit();
  2360. return 1;
  2361. } else {
  2362. foreach ($this->errors as $errmsg) {
  2363. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2364. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2365. }
  2366. $this->db->rollback();
  2367. return -1 * $error;
  2368. }
  2369. } else {
  2370. return -2;
  2371. }
  2372. }
  2373. /**
  2374. * Update a supplier order from a sales order
  2375. *
  2376. * @param User $user User that create
  2377. * @param int $idc Id of purchase order to update
  2378. * @param int $comclientid Id of sale order to use as template
  2379. * @return int <0 if KO, >0 if OK
  2380. */
  2381. public function updateFromCommandeClient($user, $idc, $comclientid)
  2382. {
  2383. $comclient = new Commande($this->db);
  2384. $comclient->fetch($comclientid);
  2385. $this->id = $idc;
  2386. $this->lines = array();
  2387. $num = count($comclient->lines);
  2388. for ($i = 0; $i < $num; $i++) {
  2389. $prod = new Product($this->db);
  2390. $label = '';
  2391. $ref = '';
  2392. if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
  2393. $label = $prod->label;
  2394. $ref = $prod->ref;
  2395. }
  2396. $sql = "INSERT INTO ".MAIN_DB_PREFIX."commande_fournisseurdet";
  2397. $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
  2398. $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
  2399. $sql .= ",".$comclient->lines[$i]->fk_product.", ".price2num($comclient->lines[$i]->price, 'MU');
  2400. $sql .= ", ".price2num($comclient->lines[$i]->qty, 'MS').", ".price2num($comclient->lines[$i]->tva_tx, 5).", ".price2num($comclient->lines[$i]->localtax1_tx, 5).", ".price2num($comclient->lines[$i]->localtax2_tx, 5).", ".price2num($comclient->lines[$i]->remise_percent, 3);
  2401. $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
  2402. if ($this->db->query($sql)) {
  2403. $this->update_price(1);
  2404. }
  2405. }
  2406. return 1;
  2407. }
  2408. /**
  2409. * Tag order with a particular status
  2410. *
  2411. * @param User $user Object user that change status
  2412. * @param int $status New status
  2413. * @return int <0 if KO, >0 if OK
  2414. */
  2415. public function setStatus($user, $status)
  2416. {
  2417. global $conf, $langs;
  2418. $error = 0;
  2419. $this->db->begin();
  2420. $sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_fournisseur';
  2421. $sql .= " SET fk_statut = ".$status;
  2422. $sql .= " WHERE rowid = ".((int) $this->id);
  2423. dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
  2424. $resql = $this->db->query($sql);
  2425. if ($resql) {
  2426. // Trigger names for each status
  2427. $triggerName = array();
  2428. $triggerName[0] = 'DRAFT';
  2429. $triggerName[1] = 'VALIDATED';
  2430. $triggerName[2] = 'APPROVED';
  2431. $triggerName[3] = 'ORDERED'; // Ordered
  2432. $triggerName[4] = 'RECEIVED_PARTIALLY';
  2433. $triggerName[5] = 'RECEIVED_COMPLETELY';
  2434. $triggerName[6] = 'CANCELED';
  2435. $triggerName[7] = 'CANCELED';
  2436. $triggerName[9] = 'REFUSED';
  2437. // Call trigger
  2438. $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
  2439. if ($result < 0) {
  2440. $error++;
  2441. }
  2442. // End call triggers
  2443. } else {
  2444. $error++;
  2445. $this->error = $this->db->lasterror();
  2446. dol_syslog(get_class($this)."::setStatus ".$this->error);
  2447. }
  2448. if (!$error) {
  2449. $this->statut = $status;
  2450. $this->db->commit();
  2451. return 1;
  2452. } else {
  2453. $this->db->rollback();
  2454. return -1;
  2455. }
  2456. }
  2457. /**
  2458. * Update line
  2459. *
  2460. * @param int $rowid Id de la ligne de facture
  2461. * @param string $desc Description de la ligne
  2462. * @param double $pu Prix unitaire
  2463. * @param double $qty Quantity
  2464. * @param double $remise_percent Percent discount on line
  2465. * @param double $txtva VAT rate
  2466. * @param double $txlocaltax1 Localtax1 tax
  2467. * @param double $txlocaltax2 Localtax2 tax
  2468. * @param double $price_base_type Type of price base
  2469. * @param int $info_bits Miscellaneous informations
  2470. * @param int $type Type of line (0=product, 1=service)
  2471. * @param int $notrigger Disable triggers
  2472. * @param integer $date_start Date start of service
  2473. * @param integer $date_end Date end of service
  2474. * @param array $array_options Extrafields array
  2475. * @param string $fk_unit Code of the unit to use. Null to use the default one
  2476. * @param double $pu_ht_devise Unit price in currency
  2477. * @param string $ref_supplier Supplier ref
  2478. * @return int < 0 if error, > 0 if ok
  2479. */
  2480. public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $price_base_type = 'HT', $info_bits = 0, $type = 0, $notrigger = 0, $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $ref_supplier = '')
  2481. {
  2482. global $mysoc, $conf, $langs;
  2483. dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
  2484. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  2485. $error = 0;
  2486. if ($this->brouillon) {
  2487. // Clean parameters
  2488. if (empty($qty)) {
  2489. $qty = 0;
  2490. }
  2491. if (empty($info_bits)) {
  2492. $info_bits = 0;
  2493. }
  2494. if (empty($txtva)) {
  2495. $txtva = 0;
  2496. }
  2497. if (empty($txlocaltax1)) {
  2498. $txlocaltax1 = 0;
  2499. }
  2500. if (empty($txlocaltax2)) {
  2501. $txlocaltax2 = 0;
  2502. }
  2503. if (empty($remise)) {
  2504. $remise = 0;
  2505. }
  2506. if (empty($remise_percent)) {
  2507. $remise_percent = 0;
  2508. }
  2509. $remise_percent = price2num($remise_percent);
  2510. $qty = price2num($qty);
  2511. if (!$qty) {
  2512. $qty = 1;
  2513. }
  2514. $pu = price2num($pu);
  2515. $pu_ht_devise = price2num($pu_ht_devise);
  2516. if (!preg_match('/\((.*)\)/', $txtva)) {
  2517. $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
  2518. }
  2519. $txlocaltax1 = price2num($txlocaltax1);
  2520. $txlocaltax2 = price2num($txlocaltax2);
  2521. // Check parameters
  2522. if ($type < 0) {
  2523. return -1;
  2524. }
  2525. if ($date_start && $date_end && $date_start > $date_end) {
  2526. $langs->load("errors");
  2527. $this->error = $langs->trans('ErrorStartDateGreaterEnd');
  2528. return -1;
  2529. }
  2530. $this->db->begin();
  2531. // Calcul du total TTC et de la TVA pour la ligne a partir de
  2532. // qty, pu, remise_percent et txtva
  2533. // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
  2534. // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
  2535. $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
  2536. // Clean vat code
  2537. $reg = array();
  2538. $vat_src_code = '';
  2539. if (preg_match('/\((.*)\)/', $txtva, $reg)) {
  2540. $vat_src_code = $reg[1];
  2541. $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
  2542. }
  2543. $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
  2544. $total_ht = $tabprice[0];
  2545. $total_tva = $tabprice[1];
  2546. $total_ttc = $tabprice[2];
  2547. $total_localtax1 = $tabprice[9];
  2548. $total_localtax2 = $tabprice[10];
  2549. $pu_ht = $tabprice[3];
  2550. $pu_tva = $tabprice[4];
  2551. $pu_ttc = $tabprice[5];
  2552. // MultiCurrency
  2553. $multicurrency_total_ht = $tabprice[16];
  2554. $multicurrency_total_tva = $tabprice[17];
  2555. $multicurrency_total_ttc = $tabprice[18];
  2556. $pu_ht_devise = $tabprice[19];
  2557. $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
  2558. $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
  2559. //Fetch current line from the database and then clone the object and set it in $oldline property
  2560. $this->line = new CommandeFournisseurLigne($this->db);
  2561. $this->line->fetch($rowid);
  2562. $oldline = clone $this->line;
  2563. $this->line->oldline = $oldline;
  2564. $this->line->context = $this->context;
  2565. $this->line->fk_commande = $this->id;
  2566. //$this->line->label=$label;
  2567. $this->line->desc = $desc;
  2568. // redefine quantity according to packaging
  2569. if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
  2570. if ($qty < $this->line->packaging) {
  2571. $qty = $this->line->packaging;
  2572. } else {
  2573. if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) {
  2574. $coeff = intval($qty / $this->line->packaging) + 1;
  2575. $qty = $this->line->packaging * $coeff;
  2576. setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs');
  2577. }
  2578. }
  2579. }
  2580. $this->line->qty = $qty;
  2581. $this->line->ref_supplier = $ref_supplier;
  2582. $this->line->vat_src_code = $vat_src_code;
  2583. $this->line->tva_tx = $txtva;
  2584. $this->line->localtax1_tx = $txlocaltax1;
  2585. $this->line->localtax2_tx = $txlocaltax2;
  2586. $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
  2587. $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
  2588. $this->line->remise_percent = $remise_percent;
  2589. $this->line->subprice = $pu_ht;
  2590. $this->line->rang = $this->rang;
  2591. $this->line->info_bits = $info_bits;
  2592. $this->line->total_ht = $total_ht;
  2593. $this->line->total_tva = $total_tva;
  2594. $this->line->total_localtax1 = $total_localtax1;
  2595. $this->line->total_localtax2 = $total_localtax2;
  2596. $this->line->total_ttc = $total_ttc;
  2597. $this->line->product_type = $type;
  2598. $this->line->special_code = (!empty($this->special_code) ? $this->special_code : 0);
  2599. $this->line->origin = $this->origin;
  2600. $this->line->fk_unit = $fk_unit;
  2601. $this->line->date_start = $date_start;
  2602. $this->line->date_end = $date_end;
  2603. // Multicurrency
  2604. $this->line->fk_multicurrency = $this->fk_multicurrency;
  2605. $this->line->multicurrency_code = $this->multicurrency_code;
  2606. $this->line->multicurrency_subprice = $pu_ht_devise;
  2607. $this->line->multicurrency_total_ht = $multicurrency_total_ht;
  2608. $this->line->multicurrency_total_tva = $multicurrency_total_tva;
  2609. $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
  2610. $this->line->subprice = $pu_ht;
  2611. $this->line->price = $this->line->subprice;
  2612. $this->line->remise_percent = $remise_percent;
  2613. if (is_array($array_options) && count($array_options) > 0) {
  2614. // We replace values in this->line->array_options only for entries defined into $array_options
  2615. foreach ($array_options as $key => $value) {
  2616. $this->line->array_options[$key] = $array_options[$key];
  2617. }
  2618. }
  2619. $result = $this->line->update($notrigger);
  2620. // Mise a jour info denormalisees au niveau facture
  2621. if ($result >= 0) {
  2622. $this->update_price('1', 'auto');
  2623. $this->db->commit();
  2624. return $result;
  2625. } else {
  2626. $this->error = $this->db->lasterror();
  2627. $this->db->rollback();
  2628. return -1;
  2629. }
  2630. } else {
  2631. $this->error = "Order status makes operation forbidden";
  2632. dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
  2633. return -2;
  2634. }
  2635. }
  2636. /**
  2637. * Initialise an instance with random values.
  2638. * Used to build previews or test instances.
  2639. * id must be 0 if object instance is a specimen.
  2640. *
  2641. * @return void
  2642. */
  2643. public function initAsSpecimen()
  2644. {
  2645. global $user, $langs, $conf;
  2646. include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
  2647. dol_syslog(get_class($this)."::initAsSpecimen");
  2648. $now = dol_now();
  2649. // Find first product
  2650. $prodid = 0;
  2651. $product = new ProductFournisseur($this->db);
  2652. $sql = "SELECT rowid";
  2653. $sql .= " FROM ".MAIN_DB_PREFIX."product";
  2654. $sql .= " WHERE entity IN (".getEntity('product').")";
  2655. $sql .= $this->db->order("rowid", "ASC");
  2656. $sql .= $this->db->plimit(1);
  2657. $resql = $this->db->query($sql);
  2658. if ($resql) {
  2659. $obj = $this->db->fetch_object($resql);
  2660. $prodid = $obj->rowid;
  2661. }
  2662. // Initialise parametres
  2663. $this->id = 0;
  2664. $this->ref = 'SPECIMEN';
  2665. $this->specimen = 1;
  2666. $this->socid = 1;
  2667. $this->date = $now;
  2668. $this->date_commande = $now;
  2669. $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
  2670. $this->cond_reglement_code = 'RECEP';
  2671. $this->mode_reglement_code = 'CHQ';
  2672. $this->note_public = 'This is a comment (public)';
  2673. $this->note_private = 'This is a comment (private)';
  2674. $this->multicurrency_tx = 1;
  2675. $this->multicurrency_code = $conf->currency;
  2676. $this->statut = 0;
  2677. // Lines
  2678. $nbp = 5;
  2679. $xnbp = 0;
  2680. while ($xnbp < $nbp) {
  2681. $line = new CommandeFournisseurLigne($this->db);
  2682. $line->desc = $langs->trans("Description")." ".$xnbp;
  2683. $line->qty = 1;
  2684. $line->subprice = 100;
  2685. $line->price = 100;
  2686. $line->tva_tx = 19.6;
  2687. $line->localtax1_tx = 0;
  2688. $line->localtax2_tx = 0;
  2689. if ($xnbp == 2) {
  2690. $line->total_ht = 50;
  2691. $line->total_ttc = 59.8;
  2692. $line->total_tva = 9.8;
  2693. $line->remise_percent = 50;
  2694. } else {
  2695. $line->total_ht = 100;
  2696. $line->total_ttc = 119.6;
  2697. $line->total_tva = 19.6;
  2698. $line->remise_percent = 00;
  2699. }
  2700. $line->fk_product = $prodid;
  2701. $this->lines[$xnbp] = $line;
  2702. $this->total_ht += $line->total_ht;
  2703. $this->total_tva += $line->total_tva;
  2704. $this->total_ttc += $line->total_ttc;
  2705. $xnbp++;
  2706. }
  2707. }
  2708. /**
  2709. * Charge les informations d'ordre info dans l'objet facture
  2710. *
  2711. * @param int $id Id de la facture a charger
  2712. * @return void
  2713. */
  2714. public function info($id)
  2715. {
  2716. $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
  2717. $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
  2718. $sql .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseur as c';
  2719. $sql .= ' WHERE c.rowid = '.((int) $id);
  2720. $result = $this->db->query($sql);
  2721. if ($result) {
  2722. if ($this->db->num_rows($result)) {
  2723. $obj = $this->db->fetch_object($result);
  2724. $this->id = $obj->rowid;
  2725. if ($obj->fk_user_author) {
  2726. $this->user_creation_id = $obj->fk_user_author;
  2727. }
  2728. if ($obj->fk_user_valid) {
  2729. $this->user_validation_id = $obj->fk_user_valid;
  2730. }
  2731. if ($obj->fk_user_modif) {
  2732. $this->user_modification_id = $obj->fk_user_modif;
  2733. }
  2734. if ($obj->fk_user_approve) {
  2735. $this->user_approve_id = $obj->fk_user_approve;
  2736. }
  2737. if ($obj->fk_user_approve2) {
  2738. $this->user_approve_id2 = $obj->fk_user_approve2;
  2739. }
  2740. $this->date_creation = $this->db->jdate($obj->datec);
  2741. $this->date_modification = $this->db->jdate($obj->datem);
  2742. $this->date_approve = $this->db->jdate($obj->datea);
  2743. $this->date_approve2 = $this->db->jdate($obj->datea2);
  2744. $this->date_validation = $this->db->jdate($obj->date_validation);
  2745. }
  2746. $this->db->free($result);
  2747. } else {
  2748. dol_print_error($this->db);
  2749. }
  2750. }
  2751. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2752. /**
  2753. * Charge indicateurs this->nb de tableau de bord
  2754. *
  2755. * @return int <0 si ko, >0 si ok
  2756. */
  2757. public function load_state_board()
  2758. {
  2759. // phpcs:enable
  2760. global $conf, $user;
  2761. $this->nb = array();
  2762. $clause = "WHERE";
  2763. $sql = "SELECT count(co.rowid) as nb";
  2764. $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur as co";
  2765. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON co.fk_soc = s.rowid";
  2766. if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
  2767. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
  2768. $sql .= " WHERE sc.fk_user = ".((int) $user->id);
  2769. $clause = "AND";
  2770. }
  2771. $sql .= " ".$clause." co.entity IN (".getEntity('supplier_order').")";
  2772. $resql = $this->db->query($sql);
  2773. if ($resql) {
  2774. while ($obj = $this->db->fetch_object($resql)) {
  2775. $this->nb["supplier_orders"] = $obj->nb;
  2776. }
  2777. $this->db->free($resql);
  2778. return 1;
  2779. } else {
  2780. dol_print_error($this->db);
  2781. $this->error = $this->db->error();
  2782. return -1;
  2783. }
  2784. }
  2785. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2786. /**
  2787. * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
  2788. *
  2789. * @param User $user Objet user
  2790. * @param int $mode "opened", "awaiting" for orders awaiting reception
  2791. * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
  2792. */
  2793. public function load_board($user, $mode = 'opened')
  2794. {
  2795. // phpcs:enable
  2796. global $conf, $langs;
  2797. $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
  2798. $sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur as c";
  2799. if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
  2800. $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
  2801. }
  2802. $sql .= " WHERE c.entity = ".$conf->entity;
  2803. if ($mode === 'awaiting') {
  2804. $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
  2805. } else {
  2806. $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
  2807. }
  2808. if ($user->socid) {
  2809. $sql .= " AND c.fk_soc = ".((int) $user->socid);
  2810. }
  2811. $resql = $this->db->query($sql);
  2812. if ($resql) {
  2813. $commandestatic = new CommandeFournisseur($this->db);
  2814. $response = new WorkboardResponse();
  2815. $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
  2816. $response->label = $langs->trans("SuppliersOrdersToProcess");
  2817. $response->labelShort = $langs->trans("Opened");
  2818. $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
  2819. $response->img = img_object('', "order");
  2820. if ($mode === 'awaiting') {
  2821. $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
  2822. $response->labelShort = $langs->trans("AwaitingReception");
  2823. $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
  2824. }
  2825. while ($obj = $this->db->fetch_object($resql)) {
  2826. $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
  2827. $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
  2828. $commandestatic->statut = $obj->fk_statut;
  2829. $response->nbtodo++;
  2830. $response->total += $obj->total_ht;
  2831. if ($commandestatic->hasDelay()) {
  2832. $response->nbtodolate++;
  2833. }
  2834. }
  2835. return $response;
  2836. } else {
  2837. $this->error = $this->db->error();
  2838. return -1;
  2839. }
  2840. }
  2841. /**
  2842. * Returns the translated input method of object (defined if $this->methode_commande_id > 0).
  2843. * This function make a sql request to get translation. No cache yet, try to not use it inside a loop.
  2844. *
  2845. * @return string
  2846. */
  2847. public function getInputMethod()
  2848. {
  2849. global $db, $langs;
  2850. if ($this->methode_commande_id > 0) {
  2851. $sql = "SELECT rowid, code, libelle as label";
  2852. $sql .= " FROM ".MAIN_DB_PREFIX.'c_input_method';
  2853. $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
  2854. $resql = $this->db->query($sql);
  2855. if ($resql) {
  2856. if ($this->db->num_rows($resql)) {
  2857. $obj = $this->db->fetch_object($resql);
  2858. $string = $langs->trans($obj->code);
  2859. if ($string == $obj->code) {
  2860. $string = $obj->label != '-' ? $obj->label : '';
  2861. }
  2862. return $string;
  2863. }
  2864. } else {
  2865. dol_print_error($this->db);
  2866. }
  2867. }
  2868. return '';
  2869. }
  2870. /**
  2871. * Create a document onto disk according to template model.
  2872. *
  2873. * @param string $modele Force template to use ('' to not force)
  2874. * @param Translate $outputlangs Object lang to use for traduction
  2875. * @param int $hidedetails Hide details of lines
  2876. * @param int $hidedesc Hide description
  2877. * @param int $hideref Hide ref
  2878. * @param null|array $moreparams Array to provide more information
  2879. * @return int < 0 if KO, 0 = no doc generated, > 0 if OK
  2880. */
  2881. public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
  2882. {
  2883. global $conf, $langs;
  2884. if (!dol_strlen($modele)) {
  2885. $modele = ''; // No doc template/generation by default
  2886. if (!empty($this->model_pdf)) {
  2887. $modele = $this->model_pdf;
  2888. } elseif (!empty($conf->global->COMMANDE_SUPPLIER_ADDON_PDF)) {
  2889. $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
  2890. }
  2891. }
  2892. if (empty($modele)) {
  2893. return 0;
  2894. } else {
  2895. $langs->load("suppliers");
  2896. $outputlangs->load("products");
  2897. $modelpath = "core/modules/supplier_order/doc/";
  2898. return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
  2899. }
  2900. }
  2901. /**
  2902. * Return the max number delivery delay in day
  2903. *
  2904. * @param Translate $langs Language object
  2905. * @return string Translated string
  2906. */
  2907. public function getMaxDeliveryTimeDay($langs)
  2908. {
  2909. if (empty($this->lines)) {
  2910. return '';
  2911. }
  2912. $obj = new ProductFournisseur($this->db);
  2913. $nb = 0;
  2914. foreach ($this->lines as $line) {
  2915. if ($line->fk_product > 0) {
  2916. $idp = $obj->find_min_price_product_fournisseur($line->fk_product, $line->qty);
  2917. if ($idp) {
  2918. $obj->fetch($idp);
  2919. if ($obj->delivery_time_days > $nb) {
  2920. $nb = $obj->delivery_time_days;
  2921. }
  2922. }
  2923. }
  2924. }
  2925. if ($nb === 0) {
  2926. return '';
  2927. } else {
  2928. return $nb.' '.$langs->trans('Days');
  2929. }
  2930. }
  2931. /**
  2932. * Returns the rights used for this class
  2933. * @return stdClass
  2934. */
  2935. public function getRights()
  2936. {
  2937. global $user;
  2938. return $user->hasRight("fournisseur", "commande");
  2939. }
  2940. /**
  2941. * Function used to replace a thirdparty id with another one.
  2942. *
  2943. * @param DoliDB $dbs Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
  2944. * @param int $origin_id Old thirdparty id
  2945. * @param int $dest_id New thirdparty id
  2946. * @return bool
  2947. */
  2948. public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
  2949. {
  2950. $tables = array(
  2951. 'commande_fournisseur'
  2952. );
  2953. return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
  2954. }
  2955. /**
  2956. * Function used to replace a product id with another one.
  2957. *
  2958. * @param DoliDB $db Database handler
  2959. * @param int $origin_id Old product id
  2960. * @param int $dest_id New product id
  2961. * @return bool
  2962. */
  2963. public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
  2964. {
  2965. $tables = array(
  2966. 'commande_fournisseurdet'
  2967. );
  2968. return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
  2969. }
  2970. /**
  2971. * Is the supplier order delayed?
  2972. * We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.
  2973. * If order has not been sent, we use the order date.
  2974. *
  2975. * @return bool True if object is delayed
  2976. */
  2977. public function hasDelay()
  2978. {
  2979. global $conf;
  2980. if (empty($this->delivery_date) && !empty($this->date_livraison)) {
  2981. $this->delivery_date = $this->date_livraison; // For backward compatibility
  2982. }
  2983. if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
  2984. $now = dol_now();
  2985. if (!empty($this->delivery_date)) {
  2986. $date_to_test = $this->delivery_date;
  2987. return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
  2988. } else {
  2989. //$date_to_test = $this->date_commande;
  2990. //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
  2991. return false;
  2992. }
  2993. } else {
  2994. $now = dol_now();
  2995. $date_to_test = $this->date_commande;
  2996. return ($this->statut > 0 && $this->statut < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
  2997. }
  2998. }
  2999. /**
  3000. * Show the customer delayed info.
  3001. * We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.
  3002. * If order has not been sent, we use the order date.
  3003. *
  3004. * @return string Show delayed information
  3005. */
  3006. public function showDelay()
  3007. {
  3008. global $conf, $langs;
  3009. if (empty($this->delivery_date) && !empty($this->date_livraison)) {
  3010. $this->delivery_date = $this->date_livraison; // For backward compatibility
  3011. }
  3012. $text = '';
  3013. if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
  3014. if (!empty($this->delivery_date)) {
  3015. $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
  3016. } else {
  3017. $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
  3018. }
  3019. } else {
  3020. $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
  3021. }
  3022. if ($text) {
  3023. $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
  3024. }
  3025. return $text;
  3026. }
  3027. /**
  3028. * Calc status regarding to dispatched stock
  3029. *
  3030. * @param User $user User action
  3031. * @param int $closeopenorder Close if received
  3032. * @param string $comment Comment
  3033. * @return int <0 if KO, 0 if not applicable, >0 if OK
  3034. */
  3035. public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
  3036. {
  3037. global $conf, $langs;
  3038. if (isModEnabled("supplier_order")) {
  3039. require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
  3040. $qtydelivered = array();
  3041. $qtywished = array();
  3042. $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
  3043. $filter = array('t.fk_commande'=>$this->id);
  3044. if (!empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
  3045. $filter['t.status'] = 1; // Restrict to lines with status validated
  3046. }
  3047. $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
  3048. if ($ret < 0) {
  3049. $this->error = $supplierorderdispatch->error; $this->errors = $supplierorderdispatch->errors;
  3050. return $ret;
  3051. } else {
  3052. if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
  3053. require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
  3054. $date_liv = dol_now();
  3055. // Build array with quantity deliverd by product
  3056. foreach ($supplierorderdispatch->lines as $line) {
  3057. $qtydelivered[$line->fk_product] += $line->qty;
  3058. }
  3059. foreach ($this->lines as $line) {
  3060. // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
  3061. if (empty($conf->global->STOCK_SUPPORTS_SERVICES) && $line->product_type > 0) {
  3062. continue;
  3063. }
  3064. $qtywished[$line->fk_product] += $line->qty;
  3065. }
  3066. //Compare array
  3067. $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
  3068. $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
  3069. $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
  3070. //var_dump(array_keys($qtydelivered));
  3071. //var_dump(array_keys($qtywished));
  3072. //var_dump($diff_array);
  3073. //var_dump($keysinwishednotindelivered);
  3074. //var_dump($keysindeliverednotinwished);
  3075. //exit;
  3076. if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everythings is received
  3077. if ($closeopenorder) {
  3078. //$ret=$this->setStatus($user,5);
  3079. $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
  3080. if ($ret < 0) {
  3081. return -1;
  3082. }
  3083. return 5;
  3084. } else {
  3085. //Diff => received partially
  3086. //$ret=$this->setStatus($user,4);
  3087. $ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
  3088. if ($ret < 0) {
  3089. return -1;
  3090. }
  3091. return 4;
  3092. }
  3093. } elseif (!empty($conf->global->SUPPLIER_ORDER_MORE_THAN_WISHED)) {
  3094. //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
  3095. $close = 0;
  3096. if (count($diff_array) > 0) {
  3097. //there are some difference between the two arrays
  3098. //scan the array of results
  3099. foreach ($diff_array as $key => $value) {
  3100. //if the quantity delivered is greater or equal to wish quantity
  3101. if ($qtydelivered[$key] >= $qtywished[$key]) {
  3102. $close++;
  3103. }
  3104. }
  3105. }
  3106. if ($close == count($diff_array)) {
  3107. //all the products are received equal or more than the wished quantity
  3108. if ($closeopenorder) {
  3109. $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
  3110. if ($ret < 0) {
  3111. return -1;
  3112. }
  3113. return 5;
  3114. } else {
  3115. //Diff => received partially
  3116. $ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
  3117. if ($ret < 0) {
  3118. return -1;
  3119. }
  3120. return 4;
  3121. }
  3122. } else {
  3123. //all the products are not received
  3124. $ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
  3125. if ($ret < 0) {
  3126. return -1;
  3127. }
  3128. return 4;
  3129. }
  3130. } else {
  3131. //Diff => received partially
  3132. $ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
  3133. if ($ret < 0) {
  3134. return -1;
  3135. }
  3136. return 4;
  3137. }
  3138. }
  3139. return 1;
  3140. }
  3141. }
  3142. return 0;
  3143. }
  3144. /**
  3145. * Load array this->receptions of lines of shipments with nb of products sent for each order line
  3146. * Note: For a dedicated shipment, the fetch_lines can be used to load the qty_asked and qty_shipped. This function is use to return qty_shipped cumulated for the order
  3147. *
  3148. * @param int $filtre_statut Filter on shipment status
  3149. * @return int <0 if KO, Nb of lines found if OK
  3150. */
  3151. public function loadReceptions($filtre_statut = -1)
  3152. {
  3153. $this->receptions = array();
  3154. dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
  3155. $sql = 'SELECT cd.rowid, cd.fk_product,';
  3156. $sql .= ' sum(cfd.qty) as qty';
  3157. $sql .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseur_dispatch as cfd,';
  3158. if ($filtre_statut >= 0) {
  3159. $sql .= ' '.MAIN_DB_PREFIX.'reception as e,';
  3160. }
  3161. $sql .= ' '.MAIN_DB_PREFIX.'commande_fournisseurdet as cd';
  3162. $sql .= ' WHERE';
  3163. if ($filtre_statut >= 0) {
  3164. $sql .= ' cfd.fk_reception = e.rowid AND';
  3165. }
  3166. $sql .= ' cfd.fk_commandefourndet = cd.rowid';
  3167. $sql .= ' AND cd.fk_commande ='.((int) $this->id);
  3168. if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
  3169. $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
  3170. }
  3171. if ($filtre_statut >= 0) {
  3172. $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
  3173. }
  3174. $sql .= ' GROUP BY cd.rowid, cd.fk_product';
  3175. $resql = $this->db->query($sql);
  3176. if ($resql) {
  3177. $num = $this->db->num_rows($resql);
  3178. $i = 0;
  3179. while ($i < $num) {
  3180. $obj = $this->db->fetch_object($resql);
  3181. empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
  3182. $i++;
  3183. }
  3184. $this->db->free($resql);
  3185. return $num;
  3186. } else {
  3187. $this->error = $this->db->lasterror();
  3188. return -1;
  3189. }
  3190. }
  3191. /**
  3192. * Return clicable link of object (with eventually picto)
  3193. *
  3194. * @param string $option Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
  3195. * @param array $arraydata Array of data
  3196. * @return string HTML Code for Kanban thumb.
  3197. */
  3198. public function getKanbanView($option = '', $arraydata = null)
  3199. {
  3200. global $langs;
  3201. $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
  3202. $return = '<div class="box-flex-item box-flex-grow-zero">';
  3203. $return .= '<div class="info-box info-box-sm">';
  3204. $return .= '<span class="info-box-icon bg-infobox-action">';
  3205. $return .= img_picto('', $this->picto);
  3206. $return .= '</span>';
  3207. $return .= '<div class="info-box-content">';
  3208. $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
  3209. $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
  3210. if (property_exists($this, 'socid') || property_exists($this, 'total_tva')) {
  3211. $return .='<br><span class="info-box-label amount">'.$this->socid.'</span>';
  3212. }
  3213. if (property_exists($this, 'billed')) {
  3214. $return .= '<br><span class="opacitymedium">'.$langs->trans("Billed").' : </span><span class="info-box-label">'.yn($this->billed).'</span>';
  3215. }
  3216. if (method_exists($this, 'getLibStatut')) {
  3217. $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
  3218. }
  3219. $return .= '</div>';
  3220. $return .= '</div>';
  3221. $return .= '</div>';
  3222. return $return;
  3223. }
  3224. }
  3225. /**
  3226. * Class to manage line orders
  3227. */
  3228. class CommandeFournisseurLigne extends CommonOrderLine
  3229. {
  3230. /**
  3231. * @var string ID to identify managed object
  3232. */
  3233. public $element = 'commande_fournisseurdet';
  3234. /**
  3235. * @var string Name of table without prefix where object is stored
  3236. */
  3237. public $table_element = 'commande_fournisseurdet';
  3238. public $oldline;
  3239. /**
  3240. * Id of parent order
  3241. * @var int
  3242. */
  3243. public $fk_commande;
  3244. // From llx_commande_fournisseurdet
  3245. /**
  3246. * @var int ID
  3247. */
  3248. public $fk_parent_line;
  3249. /**
  3250. * @var int ID
  3251. */
  3252. public $fk_facture;
  3253. public $rang = 0;
  3254. public $special_code = 0;
  3255. /**
  3256. * Unit price without taxes
  3257. * @var float
  3258. */
  3259. public $pu_ht;
  3260. public $date_start;
  3261. public $date_end;
  3262. // From llx_product_fournisseur_price
  3263. /**
  3264. * Supplier reference of price when we added the line. May have been changed after line was added.
  3265. * @var string
  3266. */
  3267. public $ref_supplier;
  3268. /**
  3269. * @var string ref supplier
  3270. * @deprecated
  3271. * @see $ref_supplier
  3272. */
  3273. public $ref_fourn;
  3274. public $remise;
  3275. /**
  3276. * Constructor
  3277. *
  3278. * @param DoliDB $db Database handler
  3279. */
  3280. public function __construct($db)
  3281. {
  3282. $this->db = $db;
  3283. }
  3284. /**
  3285. * Load line order
  3286. *
  3287. * @param int $rowid Id line order
  3288. * @return int <0 if KO, >0 if OK
  3289. */
  3290. public function fetch($rowid)
  3291. {
  3292. global $conf;
  3293. $sql = 'SELECT cd.rowid, cd.fk_commande, cd.fk_product, cd.product_type, cd.description, cd.qty, cd.tva_tx, cd.special_code,';
  3294. $sql .= ' cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.ref as ref_supplier,';
  3295. $sql .= ' cd.remise, cd.remise_percent, cd.subprice,';
  3296. $sql .= ' cd.info_bits, cd.total_ht, cd.total_tva, cd.total_ttc,';
  3297. $sql .= ' cd.total_localtax1, cd.total_localtax2,';
  3298. $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
  3299. $sql .= ' cd.date_start, cd.date_end, cd.fk_unit,';
  3300. $sql .= ' cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,';
  3301. $sql .= ' c.fk_soc as socid';
  3302. $sql .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseur as c, '.MAIN_DB_PREFIX.'commande_fournisseurdet as cd';
  3303. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON cd.fk_product = p.rowid';
  3304. $sql .= ' WHERE cd.fk_commande = c.rowid AND cd.rowid = '.((int) $rowid);
  3305. $result = $this->db->query($sql);
  3306. if ($result) {
  3307. $objp = $this->db->fetch_object($result);
  3308. if (!empty($objp)) {
  3309. $this->rowid = $objp->rowid;
  3310. $this->id = $objp->rowid;
  3311. $this->fk_commande = $objp->fk_commande;
  3312. $this->desc = $objp->description;
  3313. $this->qty = $objp->qty;
  3314. $this->ref_fourn = $objp->ref_supplier;
  3315. $this->ref_supplier = $objp->ref_supplier;
  3316. $this->subprice = $objp->subprice;
  3317. $this->tva_tx = $objp->tva_tx;
  3318. $this->localtax1_tx = $objp->localtax1_tx;
  3319. $this->localtax2_tx = $objp->localtax2_tx;
  3320. $this->localtax1_type = $objp->localtax1_type;
  3321. $this->localtax2_type = $objp->localtax2_type;
  3322. $this->remise = $objp->remise;
  3323. $this->remise_percent = $objp->remise_percent;
  3324. $this->fk_product = $objp->fk_product;
  3325. $this->info_bits = $objp->info_bits;
  3326. $this->total_ht = $objp->total_ht;
  3327. $this->total_tva = $objp->total_tva;
  3328. $this->total_localtax1 = $objp->total_localtax1;
  3329. $this->total_localtax2 = $objp->total_localtax2;
  3330. $this->total_ttc = $objp->total_ttc;
  3331. $this->product_type = $objp->product_type;
  3332. $this->special_code = $objp->special_code;
  3333. $this->ref = $objp->product_ref;
  3334. $this->product_ref = $objp->product_ref;
  3335. $this->product_label = $objp->product_label;
  3336. $this->product_desc = $objp->product_desc;
  3337. if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
  3338. // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
  3339. // Move this into another method and call it when required.
  3340. // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
  3341. $sqlsearchpackage = 'SELECT rowid, packaging FROM '.MAIN_DB_PREFIX."product_fournisseur_price";
  3342. $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('product_fournisseur_price').")";
  3343. $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
  3344. $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
  3345. $sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty); // required to be qualified
  3346. $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")"; // required to be qualified
  3347. $sqlsearchpackage .= " AND fk_soc = ".((int) $objp->socid);
  3348. $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
  3349. $sqlsearchpackage .= " LIMIT 1";
  3350. $resqlsearchpackage = $this->db->query($sqlsearchpackage);
  3351. if ($resqlsearchpackage) {
  3352. $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
  3353. if ($objsearchpackage) {
  3354. $this->fk_fournprice = $objsearchpackage->rowid;
  3355. $this->packaging = $objsearchpackage->packaging;
  3356. }
  3357. } else {
  3358. $this->error = $this->db->lasterror();
  3359. return -1;
  3360. }
  3361. }
  3362. $this->date_start = $this->db->jdate($objp->date_start);
  3363. $this->date_end = $this->db->jdate($objp->date_end);
  3364. $this->fk_unit = $objp->fk_unit;
  3365. $this->multicurrency_subprice = $objp->multicurrency_subprice;
  3366. $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
  3367. $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
  3368. $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
  3369. $this->fetch_optionals();
  3370. $this->db->free($result);
  3371. return 1;
  3372. } else {
  3373. $this->error = 'Supplier order line with id='.$rowid.' not found';
  3374. dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
  3375. return 0;
  3376. }
  3377. } else {
  3378. dol_print_error($this->db);
  3379. return -1;
  3380. }
  3381. }
  3382. /**
  3383. * Insert line into database
  3384. *
  3385. * @param int $notrigger 1 = disable triggers
  3386. * @return int <0 if KO, >0 if OK
  3387. */
  3388. public function insert($notrigger = 0)
  3389. {
  3390. global $conf, $user;
  3391. $error = 0;
  3392. dol_syslog(get_class($this)."::insert rang=".$this->rang);
  3393. // Clean parameters
  3394. if (empty($this->tva_tx)) {
  3395. $this->tva_tx = 0;
  3396. }
  3397. if (empty($this->localtax1_tx)) {
  3398. $this->localtax1_tx = 0;
  3399. }
  3400. if (empty($this->localtax2_tx)) {
  3401. $this->localtax2_tx = 0;
  3402. }
  3403. if (empty($this->localtax1_type)) {
  3404. $this->localtax1_type = '0';
  3405. }
  3406. if (empty($this->localtax2_type)) {
  3407. $this->localtax2_type = '0';
  3408. }
  3409. if (empty($this->total_localtax1)) {
  3410. $this->total_localtax1 = 0;
  3411. }
  3412. if (empty($this->total_localtax2)) {
  3413. $this->total_localtax2 = 0;
  3414. }
  3415. if (empty($this->rang)) {
  3416. $this->rang = 0;
  3417. }
  3418. if (empty($this->remise_percent)) {
  3419. $this->remise_percent = 0;
  3420. }
  3421. if (empty($this->info_bits)) {
  3422. $this->info_bits = 0;
  3423. }
  3424. if (empty($this->special_code)) {
  3425. $this->special_code = 0;
  3426. }
  3427. if (empty($this->fk_parent_line)) {
  3428. $this->fk_parent_line = 0;
  3429. }
  3430. if (empty($this->pa_ht)) {
  3431. $this->pa_ht = 0;
  3432. }
  3433. // Multicurrency
  3434. if (!empty($this->multicurrency_code)) {
  3435. list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
  3436. }
  3437. if (empty($this->fk_multicurrency)) {
  3438. $this->multicurrency_code = $conf->currency;
  3439. $this->fk_multicurrency = 0;
  3440. $this->multicurrency_tx = 1;
  3441. }
  3442. // Check parameters
  3443. if ($this->product_type < 0) {
  3444. return -1;
  3445. }
  3446. $this->db->begin();
  3447. // Insertion dans base de la ligne
  3448. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element;
  3449. $sql .= " (fk_commande, label, description, date_start, date_end,";
  3450. $sql .= " fk_product, product_type, special_code, rang,";
  3451. $sql .= " qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice, ref,";
  3452. $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_unit,";
  3453. $sql .= " fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc";
  3454. $sql .= ")";
  3455. $sql .= " VALUES (".$this->fk_commande.", '".$this->db->escape($this->label)."','".$this->db->escape($this->desc)."',";
  3456. $sql .= " ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : "null").",";
  3457. $sql .= " ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : "null").",";
  3458. if ($this->fk_product) {
  3459. $sql .= $this->fk_product.",";
  3460. } else {
  3461. $sql .= "null,";
  3462. }
  3463. $sql .= "'".$this->db->escape($this->product_type)."',";
  3464. $sql .= "'".$this->db->escape($this->special_code)."',";
  3465. $sql .= "'".$this->db->escape($this->rang)."',";
  3466. $sql .= "'".$this->db->escape($this->qty)."', ";
  3467. $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
  3468. $sql .= " ".price2num($this->tva_tx).", ";
  3469. $sql .= " ".price2num($this->localtax1_tx).",";
  3470. $sql .= " ".price2num($this->localtax2_tx).",";
  3471. $sql .= " '".$this->db->escape($this->localtax1_type)."',";
  3472. $sql .= " '".$this->db->escape($this->localtax2_type)."',";
  3473. $sql .= " ".((float) $this->remise_percent).", ".price2num($this->subprice, 'MU').", '".$this->db->escape($this->ref_supplier)."',";
  3474. $sql .= " ".price2num($this->total_ht).",";
  3475. $sql .= " ".price2num($this->total_tva).",";
  3476. $sql .= " ".price2num($this->total_localtax1).",";
  3477. $sql .= " ".price2num($this->total_localtax2).",";
  3478. $sql .= " ".price2num($this->total_ttc).",";
  3479. $sql .= ($this->fk_unit ? "'".$this->db->escape($this->fk_unit)."'" : "null");
  3480. $sql .= ", ".($this->fk_multicurrency ? ((int) $this->fk_multicurrency) : "null");
  3481. $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
  3482. $sql .= ", ".($this->multicurrency_subprice ? price2num($this->multicurrency_subprice) : '0');
  3483. $sql .= ", ".($this->multicurrency_total_ht ? price2num($this->multicurrency_total_ht) : '0');
  3484. $sql .= ", ".($this->multicurrency_total_tva ? price2num($this->multicurrency_total_tva) : '0');
  3485. $sql .= ", ".($this->multicurrency_total_ttc ? price2num($this->multicurrency_total_ttc) : '0');
  3486. $sql .= ")";
  3487. dol_syslog(get_class($this)."::insert", LOG_DEBUG);
  3488. $resql = $this->db->query($sql);
  3489. if ($resql) {
  3490. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
  3491. $this->rowid = $this->id;
  3492. if (!$error) {
  3493. $result = $this->insertExtraFields();
  3494. if ($result < 0) {
  3495. $error++;
  3496. }
  3497. }
  3498. if (!$error && !$notrigger) {
  3499. // Call trigger
  3500. $result = $this->call_trigger('LINEORDER_SUPPLIER_CREATE', $user);
  3501. if ($result < 0) {
  3502. $error++;
  3503. }
  3504. // End call triggers
  3505. }
  3506. if (!$error) {
  3507. $this->db->commit();
  3508. return 1;
  3509. }
  3510. foreach ($this->errors as $errmsg) {
  3511. dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
  3512. $this->errors[] = ($this->errors ? ', '.$errmsg : $errmsg);
  3513. }
  3514. $this->db->rollback();
  3515. return -1 * $error;
  3516. } else {
  3517. $this->errors[] = $this->db->error();
  3518. $this->db->rollback();
  3519. return -2;
  3520. }
  3521. }
  3522. /**
  3523. * Update the line object into db
  3524. *
  3525. * @param int $notrigger 1 = disable triggers
  3526. * @return int <0 si ko, >0 si ok
  3527. */
  3528. public function update($notrigger = 0)
  3529. {
  3530. global $conf, $user;
  3531. $error = 0;
  3532. $this->db->begin();
  3533. // Mise a jour ligne en base
  3534. $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
  3535. $sql .= " description='".$this->db->escape($this->desc)."'";
  3536. $sql .= ", ref='".$this->db->escape($this->ref_supplier)."'";
  3537. $sql .= ", subprice='".price2num($this->subprice)."'";
  3538. //$sql.= ",remise='".price2num($remise)."'";
  3539. $sql .= ", remise_percent='".price2num($this->remise_percent)."'";
  3540. $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
  3541. $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
  3542. $sql .= ", localtax1_tx='".price2num($this->localtax1_tx)."'";
  3543. $sql .= ", localtax2_tx='".price2num($this->localtax2_tx)."'";
  3544. $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
  3545. $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
  3546. $sql .= ", qty='".price2num($this->qty)."'";
  3547. $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
  3548. $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
  3549. $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
  3550. $sql .= ", total_ht='".price2num($this->total_ht)."'";
  3551. $sql .= ", total_tva='".price2num($this->total_tva)."'";
  3552. $sql .= ", total_localtax1='".price2num($this->total_localtax1)."'";
  3553. $sql .= ", total_localtax2='".price2num($this->total_localtax2)."'";
  3554. $sql .= ", total_ttc='".price2num($this->total_ttc)."'";
  3555. $sql .= ", product_type=".$this->product_type;
  3556. $sql .= ", special_code=".(!empty($this->special_code) ? $this->special_code : 0);
  3557. $sql .= ($this->fk_unit ? ", fk_unit='".$this->db->escape($this->fk_unit)."'" : ", fk_unit=null");
  3558. // Multicurrency
  3559. $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
  3560. $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
  3561. $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
  3562. $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
  3563. $sql .= " WHERE rowid = ".((int) $this->id);
  3564. dol_syslog(get_class($this)."::updateline", LOG_DEBUG);
  3565. $resql = $this->db->query($sql);
  3566. if ($resql) {
  3567. if (!$error) {
  3568. $result = $this->insertExtraFields();
  3569. if ($result < 0) {
  3570. $error++;
  3571. }
  3572. }
  3573. if (!$error && !$notrigger) {
  3574. // Call trigger
  3575. $result = $this->call_trigger('LINEORDER_SUPPLIER_MODIFY', $user);
  3576. if ($result < 0) {
  3577. $this->db->rollback();
  3578. return -1;
  3579. }
  3580. // End call triggers
  3581. }
  3582. if (!$error) {
  3583. $this->db->commit();
  3584. return 1;
  3585. } else {
  3586. $this->db->rollback();
  3587. return -1;
  3588. }
  3589. } else {
  3590. $this->error = $this->db->lasterror();
  3591. $this->db->rollback();
  3592. return -1;
  3593. }
  3594. }
  3595. /**
  3596. * Delete line in database
  3597. *
  3598. * @param int $notrigger 1=Disable call to triggers
  3599. * @return int <0 if KO, >0 if OK
  3600. */
  3601. public function delete($notrigger = 0)
  3602. {
  3603. global $user;
  3604. $error = 0;
  3605. $this->db->begin();
  3606. // extrafields
  3607. $result = $this->deleteExtraFields();
  3608. if ($result < 0) {
  3609. $this->db->rollback();
  3610. return -1;
  3611. }
  3612. $sql = 'DELETE FROM '.MAIN_DB_PREFIX."commande_fournisseurdet WHERE rowid=".((int) $this->id);
  3613. dol_syslog(__METHOD__, LOG_DEBUG);
  3614. $resql = $this->db->query($sql);
  3615. if ($resql) {
  3616. if (!$notrigger) {
  3617. // Call trigger
  3618. $result = $this->call_trigger('LINEORDER_SUPPLIER_DELETE', $user);
  3619. if ($result < 0) {
  3620. $error++;
  3621. }
  3622. // End call triggers
  3623. }
  3624. if (!$error) {
  3625. $this->db->commit();
  3626. return 1;
  3627. }
  3628. foreach ($this->errors as $errmsg) {
  3629. dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
  3630. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  3631. }
  3632. $this->db->rollback();
  3633. return -1 * $error;
  3634. } else {
  3635. $this->error = $this->db->lasterror();
  3636. return -1;
  3637. }
  3638. }
  3639. }