fournisseur.commande.class.php 138 KB

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