fournisseur.commande.class.php 138 KB

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