fournisseur.commande.class.php 137 KB

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