propal.class.php 152 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695
  1. <?php
  2. /* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
  4. * Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net>
  5. * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
  6. * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
  7. * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
  8. * Copyright (C) 2008 Raphael Bertrand <raphael.bertrand@resultic.fr>
  9. * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
  10. * Copyright (C) 2010-2022 Philippe Grand <philippe.grand@atoo-net.com>
  11. * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
  12. * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
  13. * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
  14. * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
  15. * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
  16. * Copyright (C) 2018-2021 Frédéric France <frederic.france@netlogic.fr>
  17. * Copyright (C) 2018 Ferran Marcet <fmarcet@2byte.es>
  18. * Copyright (C) 2022 ATM Consulting <contact@atm-consulting.fr>
  19. * Copyright (C) 2022 OpenDSI <support@open-dsi.fr>
  20. * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
  21. *
  22. * This program is free software; you can redistribute it and/or modify
  23. * it under the terms of the GNU General Public License as published by
  24. * the Free Software Foundation; either version 3 of the License, or
  25. * (at your option) any later version.
  26. *
  27. * This program is distributed in the hope that it will be useful,
  28. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  29. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  30. * GNU General Public License for more details.
  31. *
  32. * You should have received a copy of the GNU General Public License
  33. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  34. */
  35. /**
  36. * \file htdocs/comm/propal/class/propal.class.php
  37. * \brief File of class to manage proposals
  38. */
  39. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
  40. require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
  41. require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
  42. require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
  43. require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
  44. require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
  45. require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
  46. /**
  47. * Class to manage proposals
  48. */
  49. class Propal extends CommonObject
  50. {
  51. use CommonIncoterm;
  52. /**
  53. * @var string code
  54. */
  55. public $code = "";
  56. /**
  57. * @var string ID to identify managed object
  58. */
  59. public $element = 'propal';
  60. /**
  61. * @var string Name of table without prefix where object is stored
  62. */
  63. public $table_element = 'propal';
  64. /**
  65. * @var int Name of subtable line
  66. */
  67. public $table_element_line = 'propaldet';
  68. /**
  69. * @var string Fieldname with ID of parent key if this field has a parent
  70. */
  71. public $fk_element = 'fk_propal';
  72. /**
  73. * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
  74. */
  75. public $picto = 'propal';
  76. /**
  77. * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  78. * @var int
  79. */
  80. public $ismultientitymanaged = 1;
  81. /**
  82. * 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
  83. * @var integer
  84. */
  85. public $restrictiononfksoc = 1;
  86. /**
  87. * {@inheritdoc}
  88. */
  89. protected $table_ref_field = 'ref';
  90. /**
  91. * ID of the client
  92. * @var int
  93. */
  94. public $socid;
  95. /**
  96. * ID of the contact
  97. * @var int
  98. */
  99. public $contactid;
  100. public $author;
  101. /**
  102. * Ref from thirdparty
  103. * @var string
  104. * @deprecated
  105. * @see $ref_customer
  106. */
  107. public $ref_client;
  108. /**
  109. * Ref from thirdparty
  110. * @var string
  111. */
  112. public $ref_customer;
  113. /**
  114. * @var Propal oldcopy with propal properties
  115. */
  116. public $oldcopy;
  117. /**
  118. * Status of the quote
  119. * @var int
  120. * @deprecated Try to use $status now
  121. * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED
  122. */
  123. public $statut;
  124. /**
  125. * Status of the quote
  126. * @var int
  127. * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED
  128. */
  129. public $status;
  130. /**
  131. * @deprecated
  132. * @see $date_creation
  133. */
  134. public $datec;
  135. /**
  136. * @var integer|string $date_creation;
  137. */
  138. public $date_creation;
  139. /**
  140. * @deprecated
  141. * @see $date_validation
  142. */
  143. public $datev;
  144. /**
  145. * @var integer|string $date_validation;
  146. */
  147. public $date_validation;
  148. /**
  149. * @var integer|string $date_signature;
  150. */
  151. public $date_signature;
  152. /**
  153. * @var User $user_signature
  154. */
  155. public $user_signature;
  156. /**
  157. * @var integer|string date of the quote;
  158. */
  159. public $date;
  160. /**
  161. * @deprecated
  162. * @see $date
  163. */
  164. public $datep;
  165. /**
  166. * @var integer|string $delivery_date;
  167. */
  168. public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
  169. public $fin_validite;
  170. public $user_author_id;
  171. /**
  172. * @deprecated
  173. * @see $total_ht
  174. */
  175. public $price;
  176. /**
  177. * @deprecated
  178. * @see $total_tva
  179. */
  180. public $tva;
  181. /**
  182. * @deprecated
  183. * @see $total_ttc
  184. */
  185. public $total;
  186. public $cond_reglement_code; // code
  187. public $cond_reglement; // label
  188. public $cond_reglement_doc; // label doc
  189. public $mode_reglement_code; // code
  190. public $mode_reglement; // label
  191. public $deposit_percent;
  192. /**
  193. * @deprecated
  194. */
  195. public $remise_percent;
  196. /**
  197. * @deprecated
  198. */
  199. public $remise;
  200. /**
  201. * @deprecated
  202. */
  203. public $remise_absolue;
  204. /**
  205. * @var int ID
  206. * @deprecated
  207. */
  208. public $fk_address;
  209. public $address_type;
  210. public $address;
  211. /**
  212. * @var int availabilty ID
  213. */
  214. public $availability_id;
  215. /**
  216. * @var int availabilty ID
  217. * @deprecated
  218. * @see $availability_id
  219. */
  220. public $fk_availability;
  221. /**
  222. * @var string availabilty code
  223. */
  224. public $availability_code;
  225. public $duree_validite;
  226. public $demand_reason_id; // id
  227. public $demand_reason_code; // code
  228. public $demand_reason; // label
  229. public $warehouse_id;
  230. public $extraparams = array();
  231. /**
  232. * @var PropaleLigne[]
  233. */
  234. public $lines = array();
  235. /**
  236. * @var PropaleLigne
  237. */
  238. public $line;
  239. public $labelStatus = array();
  240. public $labelStatusShort = array();
  241. // Multicurrency
  242. /**
  243. * @var int ID
  244. */
  245. public $fk_multicurrency;
  246. public $multicurrency_code;
  247. public $multicurrency_tx;
  248. public $multicurrency_total_ht;
  249. public $multicurrency_total_tva;
  250. public $multicurrency_total_ttc;
  251. /**
  252. * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
  253. * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
  254. * 'label' the translation key.
  255. * 'enabled' is a condition when the field must be managed.
  256. * 'position' is the sort order of field.
  257. * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
  258. * '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)
  259. * 'noteditable' says if field is not editable (1 or 0)
  260. * '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.
  261. * 'index' if we want an index in database.
  262. * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
  263. * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
  264. * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
  265. * 'css' is the CSS style to use on field. For example: 'maxwidth200'
  266. * 'help' is a string visible as a tooltip on field
  267. * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
  268. * '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.
  269. * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
  270. * 'comment' is not used. You can store here any text of your choice. It is not used by application.
  271. *
  272. * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
  273. */
  274. // BEGIN MODULEBUILDER PROPERTIES
  275. /**
  276. * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
  277. */
  278. public $fields = array(
  279. 'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
  280. 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>15, 'index'=>1),
  281. 'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>20),
  282. 'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
  283. 'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'RefExt', 'enabled'=>1, 'visible'=>0, 'position'=>40),
  284. 'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'isModEnabled("societe")', 'visible'=>-1, 'position'=>23),
  285. 'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label'=>'Fk projet', 'enabled'=>"isModEnabled('project')", 'visible'=>-1, 'position'=>24),
  286. 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
  287. 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
  288. 'datep' =>array('type'=>'date', 'label'=>'Date', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
  289. 'fin_validite' =>array('type'=>'datetime', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
  290. 'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
  291. 'date_cloture' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
  292. 'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
  293. 'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>85),
  294. 'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
  295. 'fk_user_cloture' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user cloture', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
  296. 'price' =>array('type'=>'double', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
  297. //'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
  298. //'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
  299. //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
  300. 'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
  301. 'total_tva' =>array('type'=>'double(24,8)', 'label'=>'VAT', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
  302. 'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LocalTax1', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
  303. 'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LocalTax2', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
  304. 'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>145, 'isameasure'=>1),
  305. 'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'isModEnabled("banque")', 'visible'=>-1, 'position'=>150),
  306. 'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'Currency', 'enabled'=>1, 'visible'=>-1, 'position'=>155),
  307. 'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
  308. 'deposit_percent' =>array('type'=>'varchar(63)', 'label'=>'DepositPercent', 'enabled'=>1, 'visible'=>-1, 'position'=>161),
  309. 'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
  310. 'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>170),
  311. 'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>175),
  312. 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'PDFTemplate', 'enabled'=>1, 'visible'=>0, 'position'=>180),
  313. 'date_livraison' =>array('type'=>'date', 'label'=>'DateDeliveryPlanned', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
  314. 'fk_shipping_method' =>array('type'=>'integer', 'label'=>'ShippingMethod', 'enabled'=>1, 'visible'=>-1, 'position'=>190),
  315. 'fk_warehouse' =>array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Fk warehouse', 'enabled'=>'isModEnabled("stock")', 'visible'=>-1, 'position'=>191),
  316. 'fk_availability' =>array('type'=>'integer', 'label'=>'Availability', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
  317. 'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>0, 'position'=>200), // deprecated
  318. 'fk_input_reason' =>array('type'=>'integer', 'label'=>'InputReason', 'enabled'=>1, 'visible'=>-1, 'position'=>205),
  319. 'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
  320. 'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>220),
  321. 'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>225),
  322. 'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>1, 'visible'=>-1, 'position'=>230),
  323. 'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
  324. 'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240, 'isameasure'=>1),
  325. 'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>245, 'isameasure'=>1),
  326. 'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>250, 'isameasure'=>1),
  327. 'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>255, 'isameasure'=>1),
  328. 'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>260),
  329. 'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
  330. 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
  331. );
  332. // END MODULEBUILDER PROPERTIES
  333. /**
  334. * Draft status
  335. */
  336. const STATUS_DRAFT = 0;
  337. /**
  338. * Validated status
  339. */
  340. const STATUS_VALIDATED = 1;
  341. /**
  342. * Signed quote
  343. */
  344. const STATUS_SIGNED = 2;
  345. /**
  346. * Not signed quote
  347. */
  348. const STATUS_NOTSIGNED = 3;
  349. /**
  350. * Billed or processed quote
  351. */
  352. const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
  353. /**
  354. * Constructor
  355. *
  356. * @param DoliDB $db Database handler
  357. * @param int $socid Id third party
  358. * @param int $propalid Id proposal
  359. */
  360. public function __construct($db, $socid = 0, $propalid = 0)
  361. {
  362. global $conf, $langs;
  363. $this->db = $db;
  364. $this->socid = $socid;
  365. $this->id = $propalid;
  366. $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
  367. }
  368. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  369. /**
  370. * Add line into array ->lines
  371. * $this->thirdparty should be loaded
  372. *
  373. * @param int $idproduct Product Id to add
  374. * @param int $qty Quantity
  375. * @param int $remise_percent Discount effected on Product
  376. * @return int <0 if KO, >0 if OK
  377. *
  378. * TODO Replace calls to this function by generation objet Ligne
  379. */
  380. public function add_product($idproduct, $qty, $remise_percent = 0)
  381. {
  382. // phpcs:enable
  383. global $conf, $mysoc;
  384. if (!$qty) {
  385. $qty = 1;
  386. }
  387. dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
  388. if ($idproduct > 0) {
  389. $prod = new Product($this->db);
  390. $prod->fetch($idproduct);
  391. $productdesc = $prod->description;
  392. $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
  393. $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
  394. if (empty($tva_tx)) {
  395. $tva_npr = 0;
  396. }
  397. $vat_src_code = ''; // May be defined into tva_tx
  398. $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
  399. $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
  400. // multiprices
  401. if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
  402. $price = $prod->multiprices[$this->thirdparty->price_level];
  403. } else {
  404. $price = $prod->price;
  405. }
  406. $line = new PropaleLigne($this->db);
  407. $line->fk_product = $idproduct;
  408. $line->desc = $productdesc;
  409. $line->qty = $qty;
  410. $line->subprice = $price;
  411. $line->remise_percent = $remise_percent;
  412. $line->vat_src_code = $vat_src_code;
  413. $line->tva_tx = $tva_tx;
  414. $line->fk_unit = $prod->fk_unit;
  415. if ($tva_npr) {
  416. $line->info_bits = 1;
  417. }
  418. $this->lines[] = $line;
  419. }
  420. return 1;
  421. }
  422. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  423. /**
  424. * Adding line of fixed discount in the proposal in DB
  425. *
  426. * @param int $idremise Id of fixed discount
  427. * @return int >0 if OK, <0 if KO
  428. */
  429. public function insert_discount($idremise)
  430. {
  431. // phpcs:enable
  432. global $langs;
  433. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  434. include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  435. $this->db->begin();
  436. $remise = new DiscountAbsolute($this->db);
  437. $result = $remise->fetch($idremise);
  438. if ($result > 0) {
  439. if ($remise->fk_facture) { // Protection against multiple submission
  440. $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
  441. $this->db->rollback();
  442. return -5;
  443. }
  444. $line = new PropaleLigne($this->db);
  445. $this->line->context = $this->context;
  446. $line->fk_propal = $this->id;
  447. $line->fk_remise_except = $remise->id;
  448. $line->desc = $remise->description; // Description ligne
  449. $line->vat_src_code = $remise->vat_src_code;
  450. $line->tva_tx = $remise->tva_tx;
  451. $line->subprice = -$remise->amount_ht;
  452. $line->fk_product = 0; // Id produit predefined
  453. $line->qty = 1;
  454. $line->remise_percent = 0;
  455. $line->rang = -1;
  456. $line->info_bits = 2;
  457. // TODO deprecated
  458. $line->price = -$remise->amount_ht;
  459. $line->total_ht = -$remise->amount_ht;
  460. $line->total_tva = -$remise->amount_tva;
  461. $line->total_ttc = -$remise->amount_ttc;
  462. $result = $line->insert();
  463. if ($result > 0) {
  464. $result = $this->update_price(1);
  465. if ($result > 0) {
  466. $this->db->commit();
  467. return 1;
  468. } else {
  469. $this->db->rollback();
  470. return -1;
  471. }
  472. } else {
  473. $this->error = $line->error;
  474. $this->errors = $line->errors;
  475. $this->db->rollback();
  476. return -2;
  477. }
  478. } else {
  479. $this->db->rollback();
  480. return -2;
  481. }
  482. }
  483. /**
  484. * Add a proposal line into database (linked to product/service or not)
  485. * The parameters are already supposed to be appropriate and with final values to the call
  486. * of this method. Also, for the VAT rate, it must have already been defined
  487. * by whose calling the method get_default_tva (societe_vendeuse, societe_acheteuse, '' product)
  488. * and desc must already have the right value (it's up to the caller to manage multilanguage)
  489. *
  490. * @param string $desc Description of line
  491. * @param float $pu_ht Unit price
  492. * @param float $qty Quantity
  493. * @param float $txtva Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
  494. * @param float $txlocaltax1 Local tax 1 rate (deprecated, use instead txtva with code inside)
  495. * @param float $txlocaltax2 Local tax 2 rate (deprecated, use instead txtva with code inside)
  496. * @param int $fk_product Product/Service ID predefined
  497. * @param float $remise_percent Pourcentage de remise de la ligne
  498. * @param string $price_base_type HT or TTC
  499. * @param float $pu_ttc Prix unitaire TTC
  500. * @param int $info_bits Bits for type of lines
  501. * @param int $type Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
  502. * @param int $rang Position of line
  503. * @param int $special_code Special code (also used by externals modules!)
  504. * @param int $fk_parent_line Id of parent line
  505. * @param int $fk_fournprice Id supplier price
  506. * @param int $pa_ht Buying price without tax
  507. * @param string $label ???
  508. * @param int $date_start Start date of the line
  509. * @param int $date_end End date of the line
  510. * @param array $array_options extrafields array
  511. * @param string $fk_unit Code of the unit to use. Null to use the default one
  512. * @param string $origin Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
  513. * @param int $origin_id Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
  514. * @param double $pu_ht_devise Unit price in currency
  515. * @param int $fk_remise_except Id discount if line is from a discount
  516. * @param int $noupdateafterinsertline No update after insert of line
  517. * @return int >0 if OK, <0 if KO
  518. * @see add_product()
  519. */
  520. public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
  521. {
  522. global $mysoc, $conf, $langs;
  523. dol_syslog(get_class($this)."::addline propalid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_except=$remise_percent, price_base_type=$price_base_type, pu_ttc=$pu_ttc, info_bits=$info_bits, type=$type, fk_remise_except=".$fk_remise_except);
  524. if ($this->statut == self::STATUS_DRAFT) {
  525. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  526. // Clean parameters
  527. if (empty($remise_percent)) {
  528. $remise_percent = 0;
  529. }
  530. if (empty($qty)) {
  531. $qty = 0;
  532. }
  533. if (empty($info_bits)) {
  534. $info_bits = 0;
  535. }
  536. if (empty($rang)) {
  537. $rang = 0;
  538. }
  539. if (empty($fk_parent_line) || $fk_parent_line < 0) {
  540. $fk_parent_line = 0;
  541. }
  542. $remise_percent = price2num($remise_percent);
  543. $qty = price2num($qty);
  544. $pu_ht = price2num($pu_ht);
  545. $pu_ht_devise = price2num($pu_ht_devise);
  546. $pu_ttc = price2num($pu_ttc);
  547. if (!preg_match('/\((.*)\)/', $txtva)) {
  548. $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
  549. }
  550. $txlocaltax1 = price2num($txlocaltax1);
  551. $txlocaltax2 = price2num($txlocaltax2);
  552. $pa_ht = price2num($pa_ht);
  553. if ($price_base_type == 'HT') {
  554. $pu = $pu_ht;
  555. } else {
  556. $pu = $pu_ttc;
  557. }
  558. // Check parameters
  559. if ($type < 0) {
  560. return -1;
  561. }
  562. if ($date_start && $date_end && $date_start > $date_end) {
  563. $langs->load("errors");
  564. $this->error = $langs->trans('ErrorStartDateGreaterEnd');
  565. return -1;
  566. }
  567. $this->db->begin();
  568. $product_type = $type;
  569. if (!empty($fk_product) && $fk_product > 0) {
  570. $product = new Product($this->db);
  571. $result = $product->fetch($fk_product);
  572. $product_type = $product->type;
  573. if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
  574. $langs->load("errors");
  575. $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
  576. $this->db->rollback();
  577. return -3;
  578. }
  579. }
  580. // Calcul du total TTC et de la TVA pour la ligne a partir de
  581. // qty, pu, remise_percent et txtva
  582. // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
  583. // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
  584. $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
  585. // Clean vat code
  586. $reg = array();
  587. $vat_src_code = '';
  588. $reg = array();
  589. if (preg_match('/\((.*)\)/', $txtva, $reg)) {
  590. $vat_src_code = $reg[1];
  591. $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
  592. }
  593. $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
  594. $total_ht = $tabprice[0];
  595. $total_tva = $tabprice[1];
  596. $total_ttc = $tabprice[2];
  597. $total_localtax1 = $tabprice[9];
  598. $total_localtax2 = $tabprice[10];
  599. $pu_ht = $tabprice[3];
  600. $pu_tva = $tabprice[4];
  601. $pu_ttc = $tabprice[5];
  602. // MultiCurrency
  603. $multicurrency_total_ht = $tabprice[16];
  604. $multicurrency_total_tva = $tabprice[17];
  605. $multicurrency_total_ttc = $tabprice[18];
  606. $pu_ht_devise = $tabprice[19];
  607. // Rang to use
  608. $ranktouse = $rang;
  609. if ($ranktouse == -1) {
  610. $rangmax = $this->line_max($fk_parent_line);
  611. $ranktouse = $rangmax + 1;
  612. }
  613. // TODO A virer
  614. // Anciens indicateurs: $price, $remise (a ne plus utiliser)
  615. $price = $pu;
  616. $remise = 0;
  617. if ($remise_percent > 0) {
  618. $remise = round(($pu * $remise_percent / 100), 2);
  619. $price = $pu - $remise;
  620. }
  621. // Insert line
  622. $this->line = new PropaleLigne($this->db);
  623. $this->line->context = $this->context;
  624. $this->line->fk_propal = $this->id;
  625. $this->line->label = $label;
  626. $this->line->desc = $desc;
  627. $this->line->qty = $qty;
  628. $this->line->vat_src_code = $vat_src_code;
  629. $this->line->tva_tx = $txtva;
  630. $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
  631. $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
  632. $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
  633. $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
  634. $this->line->fk_product = $fk_product;
  635. $this->line->product_type = $type;
  636. $this->line->fk_remise_except = $fk_remise_except;
  637. $this->line->remise_percent = $remise_percent;
  638. $this->line->subprice = $pu_ht;
  639. $this->line->rang = $ranktouse;
  640. $this->line->info_bits = $info_bits;
  641. $this->line->total_ht = $total_ht;
  642. $this->line->total_tva = $total_tva;
  643. $this->line->total_localtax1 = $total_localtax1;
  644. $this->line->total_localtax2 = $total_localtax2;
  645. $this->line->total_ttc = $total_ttc;
  646. $this->line->special_code = $special_code;
  647. $this->line->fk_parent_line = $fk_parent_line;
  648. $this->line->fk_unit = $fk_unit;
  649. $this->line->date_start = $date_start;
  650. $this->line->date_end = $date_end;
  651. $this->line->fk_fournprice = $fk_fournprice;
  652. $this->line->pa_ht = $pa_ht;
  653. $this->line->origin_id = $origin_id;
  654. $this->line->origin = $origin;
  655. // Multicurrency
  656. $this->line->fk_multicurrency = $this->fk_multicurrency;
  657. $this->line->multicurrency_code = $this->multicurrency_code;
  658. $this->line->multicurrency_subprice = $pu_ht_devise;
  659. $this->line->multicurrency_total_ht = $multicurrency_total_ht;
  660. $this->line->multicurrency_total_tva = $multicurrency_total_tva;
  661. $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
  662. // Mise en option de la ligne
  663. if (empty($qty) && empty($special_code)) {
  664. $this->line->special_code = 3;
  665. }
  666. // TODO deprecated
  667. $this->line->price = $price;
  668. if (is_array($array_options) && count($array_options) > 0) {
  669. $this->line->array_options = $array_options;
  670. }
  671. $result = $this->line->insert();
  672. if ($result > 0) {
  673. // Reorder if child line
  674. if (!empty($fk_parent_line)) {
  675. $this->line_order(true, 'DESC');
  676. } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
  677. $linecount = count($this->lines);
  678. for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
  679. $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
  680. }
  681. }
  682. // Mise a jour informations denormalisees au niveau de la propale meme
  683. if (empty($noupdateafterinsertline)) {
  684. $result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
  685. }
  686. if ($result > 0) {
  687. $this->db->commit();
  688. return $this->line->id;
  689. } else {
  690. $this->error = $this->db->error();
  691. $this->db->rollback();
  692. return -1;
  693. }
  694. } else {
  695. $this->error = $this->line->error;
  696. $this->errors = $this->line->errors;
  697. $this->db->rollback();
  698. return -2;
  699. }
  700. } else {
  701. dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
  702. return -3;
  703. }
  704. }
  705. /**
  706. * Update a proposal line
  707. *
  708. * @param int $rowid Id of line
  709. * @param float $pu Unit price (HT or TTC depending on price_base_type)
  710. * @param float $qty Quantity
  711. * @param float $remise_percent Discount on line
  712. * @param float $txtva VAT Rate (Can be '1.23' or '1.23 (ABC)')
  713. * @param float $txlocaltax1 Local tax 1 rate
  714. * @param float $txlocaltax2 Local tax 2 rate
  715. * @param string $desc Description
  716. * @param string $price_base_type HT or TTC
  717. * @param int $info_bits Miscellaneous informations
  718. * @param int $special_code Special code (also used by externals modules!)
  719. * @param int $fk_parent_line Id of parent line (0 in most cases, used by modules adding sublevels into lines).
  720. * @param int $skip_update_total Keep fields total_xxx to 0 (used for special lines by some modules)
  721. * @param int $fk_fournprice Id of origin supplier price
  722. * @param int $pa_ht Price (without tax) of product when it was bought
  723. * @param string $label ???
  724. * @param int $type 0/1=Product/service
  725. * @param int $date_start Start date of the line
  726. * @param int $date_end End date of the line
  727. * @param array $array_options extrafields array
  728. * @param string $fk_unit Code of the unit to use. Null to use the default one
  729. * @param double $pu_ht_devise Unit price in currency
  730. * @param int $notrigger disable line update trigger
  731. * @param integer $rang line rank
  732. * @return int 0 if OK, <0 if KO
  733. */
  734. public function updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $desc = '', $price_base_type = 'HT', $info_bits = 0, $special_code = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $type = 0, $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $rang = 0)
  735. {
  736. global $mysoc, $langs;
  737. dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
  738. txtva=$txtva, desc=$desc, price_base_type=$price_base_type, info_bits=$info_bits, special_code=$special_code, fk_parent_line=$fk_parent_line, pa_ht=$pa_ht, type=$type, date_start=$date_start, date_end=$date_end");
  739. include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
  740. // Clean parameters
  741. $remise_percent = price2num($remise_percent);
  742. $qty = price2num($qty);
  743. $pu = price2num($pu);
  744. $pu_ht_devise = price2num($pu_ht_devise);
  745. if (!preg_match('/\((.*)\)/', $txtva)) {
  746. $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
  747. }
  748. $txlocaltax1 = price2num($txlocaltax1);
  749. $txlocaltax2 = price2num($txlocaltax2);
  750. $pa_ht = price2num($pa_ht);
  751. if (empty($qty) && empty($special_code)) {
  752. $special_code = 3; // Set option tag
  753. }
  754. if (!empty($qty) && $special_code == 3) {
  755. $special_code = 0; // Remove option tag
  756. }
  757. if (empty($type)) {
  758. $type = 0;
  759. }
  760. if ($date_start && $date_end && $date_start > $date_end) {
  761. $langs->load("errors");
  762. $this->error = $langs->trans('ErrorStartDateGreaterEnd');
  763. return -1;
  764. }
  765. if ($this->statut == self::STATUS_DRAFT) {
  766. $this->db->begin();
  767. // Calcul du total TTC et de la TVA pour la ligne a partir de
  768. // qty, pu, remise_percent et txtva
  769. // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
  770. // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
  771. $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
  772. // Clean vat code
  773. $reg = array();
  774. $vat_src_code = '';
  775. if (preg_match('/\((.*)\)/', $txtva, $reg)) {
  776. $vat_src_code = $reg[1];
  777. $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
  778. }
  779. // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
  780. $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
  781. $total_ht = $tabprice[0];
  782. $total_tva = $tabprice[1];
  783. $total_ttc = $tabprice[2];
  784. $total_localtax1 = $tabprice[9];
  785. $total_localtax2 = $tabprice[10];
  786. $pu_ht = $tabprice[3];
  787. $pu_tva = $tabprice[4];
  788. $pu_ttc = $tabprice[5];
  789. // MultiCurrency
  790. $multicurrency_total_ht = $tabprice[16];
  791. $multicurrency_total_tva = $tabprice[17];
  792. $multicurrency_total_ttc = $tabprice[18];
  793. $pu_ht_devise = $tabprice[19];
  794. // Anciens indicateurs: $price, $remise (a ne plus utiliser)
  795. $price = $pu;
  796. $remise = 0;
  797. if ($remise_percent > 0) {
  798. $remise = round(($pu * $remise_percent / 100), 2);
  799. $price = $pu - $remise;
  800. }
  801. //Fetch current line from the database and then clone the object and set it in $oldline property
  802. $line = new PropaleLigne($this->db);
  803. $line->fetch($rowid);
  804. $staticline = clone $line;
  805. $line->oldline = $staticline;
  806. $this->line = $line;
  807. $this->line->context = $this->context;
  808. $this->line->rang = $rang;
  809. // Reorder if fk_parent_line change
  810. if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
  811. $rangmax = $this->line_max($fk_parent_line);
  812. $this->line->rang = $rangmax + 1;
  813. }
  814. $this->line->id = $rowid;
  815. $this->line->label = $label;
  816. $this->line->desc = $desc;
  817. $this->line->qty = $qty;
  818. $this->line->product_type = $type;
  819. $this->line->vat_src_code = $vat_src_code;
  820. $this->line->tva_tx = $txtva;
  821. $this->line->localtax1_tx = $txlocaltax1;
  822. $this->line->localtax2_tx = $txlocaltax2;
  823. $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
  824. $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
  825. $this->line->remise_percent = $remise_percent;
  826. $this->line->subprice = $pu_ht;
  827. $this->line->info_bits = $info_bits;
  828. $this->line->total_ht = $total_ht;
  829. $this->line->total_tva = $total_tva;
  830. $this->line->total_localtax1 = $total_localtax1;
  831. $this->line->total_localtax2 = $total_localtax2;
  832. $this->line->total_ttc = $total_ttc;
  833. $this->line->special_code = $special_code;
  834. $this->line->fk_parent_line = $fk_parent_line;
  835. $this->line->skip_update_total = $skip_update_total;
  836. $this->line->fk_unit = $fk_unit;
  837. $this->line->fk_fournprice = $fk_fournprice;
  838. $this->line->pa_ht = $pa_ht;
  839. $this->line->date_start = $date_start;
  840. $this->line->date_end = $date_end;
  841. if (is_array($array_options) && count($array_options) > 0) {
  842. // We replace values in this->line->array_options only for entries defined into $array_options
  843. foreach ($array_options as $key => $value) {
  844. $this->line->array_options[$key] = $array_options[$key];
  845. }
  846. }
  847. // Multicurrency
  848. $this->line->multicurrency_subprice = $pu_ht_devise;
  849. $this->line->multicurrency_total_ht = $multicurrency_total_ht;
  850. $this->line->multicurrency_total_tva = $multicurrency_total_tva;
  851. $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
  852. $result = $this->line->update($notrigger);
  853. if ($result > 0) {
  854. // Reorder if child line
  855. if (!empty($fk_parent_line)) {
  856. $this->line_order(true, 'DESC');
  857. }
  858. $this->update_price(1, 'auto');
  859. // $this is Propal
  860. // $this->fk_propal = $this->id;
  861. // $this->rowid = $rowid;
  862. $this->db->commit();
  863. return $result;
  864. } else {
  865. $this->error = $this->line->error;
  866. $this->errors = $this->line->errors;
  867. $this->db->rollback();
  868. return -1;
  869. }
  870. } else {
  871. dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
  872. return -2;
  873. }
  874. }
  875. /**
  876. * Delete detail line
  877. *
  878. * @param int $lineid Id of line to delete
  879. * @param int $id Id of object (for a check)
  880. * @return int >0 if OK, <0 if KO
  881. */
  882. public function deleteline($lineid, $id = 0)
  883. {
  884. global $user;
  885. if ($this->statut == self::STATUS_DRAFT) {
  886. $this->db->begin();
  887. $line = new PropaleLigne($this->db);
  888. $line->context = $this->context;
  889. // Load data
  890. $line->fetch($lineid);
  891. if ($id > 0 && $line->fk_propal != $id) {
  892. $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
  893. return -1;
  894. }
  895. // Memorize previous line for triggers
  896. $staticline = clone $line;
  897. $line->oldline = $staticline;
  898. if ($line->delete($user) > 0) {
  899. $this->update_price(1);
  900. $this->db->commit();
  901. return 1;
  902. } else {
  903. $this->error = $line->error;
  904. $this->errors = $line->errors;
  905. $this->db->rollback();
  906. return -1;
  907. }
  908. } else {
  909. $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
  910. return -2;
  911. }
  912. }
  913. /**
  914. * Create commercial proposal into database
  915. * this->ref can be set or empty. If empty, we will use "(PROVid)"
  916. *
  917. * @param User $user User that create
  918. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  919. * @return int <0 if KO, >=0 if OK
  920. */
  921. public function create($user, $notrigger = 0)
  922. {
  923. global $conf, $hookmanager, $mysoc;
  924. $error = 0;
  925. $now = dol_now();
  926. // Clean parameters
  927. if (empty($this->date)) {
  928. $this->date = $this->datep;
  929. }
  930. $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
  931. if (empty($this->availability_id)) {
  932. $this->availability_id = 0;
  933. }
  934. if (empty($this->demand_reason_id)) {
  935. $this->demand_reason_id = 0;
  936. }
  937. // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
  938. if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
  939. list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
  940. } else {
  941. $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
  942. }
  943. if (empty($this->fk_multicurrency)) {
  944. $this->multicurrency_code = $conf->currency;
  945. $this->fk_multicurrency = 0;
  946. $this->multicurrency_tx = 1;
  947. }
  948. // Set tmp vars
  949. $delivery_date = $this->delivery_date;
  950. dol_syslog(get_class($this)."::create");
  951. // Check parameters
  952. $result = $this->fetch_thirdparty();
  953. if ($result < 0) {
  954. $this->error = "Failed to fetch company";
  955. dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
  956. return -3;
  957. }
  958. // Check parameters
  959. if (!empty($this->ref)) { // We check that ref is not already used
  960. $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
  961. if ($result > 0) {
  962. $this->error = 'ErrorRefAlreadyExists';
  963. dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
  964. $this->db->rollback();
  965. return -1;
  966. }
  967. }
  968. if (empty($this->date)) {
  969. $this->error = "Date of proposal is required";
  970. dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
  971. return -4;
  972. }
  973. $this->db->begin();
  974. // Insert into database
  975. $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
  976. $sql .= "fk_soc";
  977. $sql .= ", price";
  978. $sql .= ", remise"; // deprecated
  979. $sql .= ", remise_percent"; // deprecated
  980. $sql .= ", remise_absolue"; // deprecated
  981. $sql .= ", total_tva";
  982. $sql .= ", total_ttc";
  983. $sql .= ", datep";
  984. $sql .= ", datec";
  985. $sql .= ", ref";
  986. $sql .= ", fk_user_author";
  987. $sql .= ", note_private";
  988. $sql .= ", note_public";
  989. $sql .= ", model_pdf";
  990. $sql .= ", fin_validite";
  991. $sql .= ", fk_cond_reglement";
  992. $sql .= ", deposit_percent";
  993. $sql .= ", fk_mode_reglement";
  994. $sql .= ", fk_account";
  995. $sql .= ", ref_client";
  996. $sql .= ", ref_ext";
  997. $sql .= ", date_livraison";
  998. $sql .= ", fk_shipping_method";
  999. $sql .= ", fk_warehouse";
  1000. $sql .= ", fk_availability";
  1001. $sql .= ", fk_input_reason";
  1002. $sql .= ", fk_projet";
  1003. $sql .= ", fk_incoterms";
  1004. $sql .= ", location_incoterms";
  1005. $sql .= ", entity";
  1006. $sql .= ", fk_multicurrency";
  1007. $sql .= ", multicurrency_code";
  1008. $sql .= ", multicurrency_tx";
  1009. $sql .= ") ";
  1010. $sql .= " VALUES (";
  1011. $sql .= $this->socid;
  1012. $sql .= ", 0";
  1013. $sql .= ", ".((float) $this->remise); // deprecated
  1014. $sql .= ", ".($this->remise_percent ? ((float) $this->remise_percent) : 'NULL'); // deprecated
  1015. $sql .= ", ".($this->remise_absolue ? ((float) $this->remise_absolue) : 'NULL'); // deprecated
  1016. $sql .= ", 0";
  1017. $sql .= ", 0";
  1018. $sql .= ", '".$this->db->idate($this->date)."'";
  1019. $sql .= ", '".$this->db->idate($now)."'";
  1020. $sql .= ", '(PROV)'";
  1021. $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
  1022. $sql .= ", '".$this->db->escape($this->note_private)."'";
  1023. $sql .= ", '".$this->db->escape($this->note_public)."'";
  1024. $sql .= ", '".$this->db->escape($this->model_pdf)."'";
  1025. $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
  1026. $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
  1027. $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
  1028. $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
  1029. $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
  1030. $sql .= ", '".$this->db->escape($this->ref_client)."'";
  1031. $sql .= ", '".$this->db->escape($this->ref_ext)."'";
  1032. $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
  1033. $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
  1034. $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
  1035. $sql .= ", ".$this->availability_id;
  1036. $sql .= ", ".$this->demand_reason_id;
  1037. $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
  1038. $sql .= ", ".(int) $this->fk_incoterms;
  1039. $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
  1040. $sql .= ", ".setEntity($this);
  1041. $sql .= ", ".(int) $this->fk_multicurrency;
  1042. $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
  1043. $sql .= ", ".(double) $this->multicurrency_tx;
  1044. $sql .= ")";
  1045. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  1046. $resql = $this->db->query($sql);
  1047. if ($resql) {
  1048. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
  1049. if ($this->id) {
  1050. $this->ref = '(PROV'.$this->id.')';
  1051. $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
  1052. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  1053. $resql = $this->db->query($sql);
  1054. if (!$resql) {
  1055. $error++;
  1056. }
  1057. if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
  1058. $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
  1059. }
  1060. // Add object linked
  1061. if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
  1062. foreach ($this->linked_objects as $origin => $tmp_origin_id) {
  1063. 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, ...))
  1064. foreach ($tmp_origin_id as $origin_id) {
  1065. $ret = $this->add_object_linked($origin, $origin_id);
  1066. if (!$ret) {
  1067. $this->error = $this->db->lasterror();
  1068. $error++;
  1069. }
  1070. }
  1071. } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
  1072. {
  1073. $origin_id = $tmp_origin_id;
  1074. $ret = $this->add_object_linked($origin, $origin_id);
  1075. if (!$ret) {
  1076. $this->error = $this->db->lasterror();
  1077. $error++;
  1078. }
  1079. }
  1080. }
  1081. }
  1082. /*
  1083. * Insertion du detail des produits dans la base
  1084. * Insert products detail in database
  1085. */
  1086. if (!$error) {
  1087. $fk_parent_line = 0;
  1088. $num = count($this->lines);
  1089. for ($i = 0; $i < $num; $i++) {
  1090. if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
  1091. // Convert into object this->lines[$i].
  1092. $line = (object) $this->lines[$i];
  1093. } else {
  1094. $line = $this->lines[$i];
  1095. }
  1096. // Reset fk_parent_line for line that are not child lines or special product
  1097. if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
  1098. $fk_parent_line = 0;
  1099. }
  1100. // Complete vat rate with code
  1101. $vatrate = $line->tva_tx;
  1102. if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate)) {
  1103. $vatrate .= ' ('.$line->vat_src_code.')';
  1104. }
  1105. if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
  1106. $originid = $line->origin_id;
  1107. $origintype = $line->origin;
  1108. } else {
  1109. $originid = $line->id;
  1110. $origintype = $this->element;
  1111. }
  1112. $result = $this->addline(
  1113. $line->desc,
  1114. $line->subprice,
  1115. $line->qty,
  1116. $vatrate,
  1117. $line->localtax1_tx,
  1118. $line->localtax2_tx,
  1119. $line->fk_product,
  1120. $line->remise_percent,
  1121. 'HT',
  1122. 0,
  1123. $line->info_bits,
  1124. $line->product_type,
  1125. $line->rang,
  1126. $line->special_code,
  1127. $fk_parent_line,
  1128. $line->fk_fournprice,
  1129. $line->pa_ht,
  1130. $line->label,
  1131. $line->date_start,
  1132. $line->date_end,
  1133. $line->array_options,
  1134. $line->fk_unit,
  1135. $origintype,
  1136. $originid,
  1137. 0,
  1138. 0,
  1139. 1
  1140. );
  1141. if ($result < 0) {
  1142. $error++;
  1143. $this->error = $this->db->error;
  1144. dol_print_error($this->db);
  1145. break;
  1146. }
  1147. // Set the id on created row
  1148. $line->id = $result;
  1149. // Defined the new fk_parent_line
  1150. if ($result > 0 && $line->product_type == 9) {
  1151. $fk_parent_line = $result;
  1152. }
  1153. }
  1154. }
  1155. // Set delivery address
  1156. /*if (! $error && $this->fk_delivery_address)
  1157. {
  1158. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  1159. $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
  1160. $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
  1161. $sql.= " AND entity = ".setEntity($this);
  1162. $result=$this->db->query($sql);
  1163. }*/
  1164. if (!$error) {
  1165. // Mise a jour infos denormalisees
  1166. $resql = $this->update_price(1, 'auto', 0, $mysoc);
  1167. if ($resql) {
  1168. $action = 'update';
  1169. // Actions on extra fields
  1170. if (!$error) {
  1171. $result = $this->insertExtraFields();
  1172. if ($result < 0) {
  1173. $error++;
  1174. }
  1175. }
  1176. if (!$error && !$notrigger) {
  1177. // Call trigger
  1178. $result = $this->call_trigger('PROPAL_CREATE', $user);
  1179. if ($result < 0) {
  1180. $error++;
  1181. }
  1182. // End call triggers
  1183. }
  1184. } else {
  1185. $this->error = $this->db->lasterror();
  1186. $error++;
  1187. }
  1188. }
  1189. } else {
  1190. $this->error = $this->db->lasterror();
  1191. $error++;
  1192. }
  1193. if (!$error) {
  1194. $this->db->commit();
  1195. dol_syslog(get_class($this)."::create done id=".$this->id);
  1196. return $this->id;
  1197. } else {
  1198. $this->db->rollback();
  1199. return -2;
  1200. }
  1201. } else {
  1202. $this->error = $this->db->lasterror();
  1203. $this->db->rollback();
  1204. return -1;
  1205. }
  1206. }
  1207. /**
  1208. * Load an object from its id and create a new one in database
  1209. *
  1210. * @param User $user User making the clone
  1211. * @param int $socid Id of thirdparty
  1212. * @param int $forceentity Entity id to force
  1213. * @param bool $update_prices [=false] Update prices if true
  1214. * @param bool $update_desc [=false] Update description if true
  1215. * @return int New id of clone
  1216. */
  1217. public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
  1218. {
  1219. global $conf, $hookmanager, $mysoc;
  1220. dol_include_once('/projet/class/project.class.php');
  1221. $error = 0;
  1222. $now = dol_now();
  1223. dol_syslog(__METHOD__, LOG_DEBUG);
  1224. $object = new self($this->db);
  1225. $this->db->begin();
  1226. // Load source object
  1227. $object->fetch($this->id);
  1228. $objsoc = new Societe($this->db);
  1229. // Change socid if needed
  1230. if (!empty($socid) && $socid != $object->socid) {
  1231. if ($objsoc->fetch($socid) > 0) {
  1232. $object->socid = $objsoc->id;
  1233. $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
  1234. $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
  1235. $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
  1236. $object->fk_delivery_address = '';
  1237. /*if (isModEnabled('project'))
  1238. {
  1239. $project = new Project($db);
  1240. if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
  1241. if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
  1242. else $clonedObj->fk_project = '';
  1243. } else {
  1244. $clonedObj->fk_project = '';
  1245. }
  1246. }*/
  1247. $object->fk_project = ''; // A cloned proposal is set by default to no project.
  1248. }
  1249. // reset ref_client
  1250. $object->ref_client = '';
  1251. // TODO Change product price if multi-prices
  1252. } else {
  1253. $objsoc->fetch($object->socid);
  1254. }
  1255. // update prices
  1256. if ($update_prices === true || $update_desc === true) {
  1257. if ($objsoc->id > 0 && !empty($object->lines)) {
  1258. if ($update_prices === true && !empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
  1259. // If price per customer
  1260. require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
  1261. }
  1262. foreach ($object->lines as $line) {
  1263. $line->id = 0;
  1264. if ($line->fk_product > 0) {
  1265. $prod = new Product($this->db);
  1266. $res = $prod->fetch($line->fk_product);
  1267. if ($res > 0) {
  1268. if ($update_prices === true) {
  1269. $pu_ht = $prod->price;
  1270. $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
  1271. $remise_percent = $objsoc->remise_percent;
  1272. if (!empty($conf->global->PRODUIT_MULTIPRICES) && $objsoc->price_level > 0) {
  1273. $pu_ht = $prod->multiprices[$objsoc->price_level];
  1274. if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
  1275. if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
  1276. $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
  1277. }
  1278. }
  1279. } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
  1280. $prodcustprice = new ProductCustomerPrice($this->db);
  1281. $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
  1282. $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
  1283. if ($result) {
  1284. // If there is some prices specific to the customer
  1285. if (count($prodcustprice->lines) > 0) {
  1286. $pu_ht = price($prodcustprice->lines[0]->price);
  1287. $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
  1288. if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\(.*\)/', $tva_tx)) {
  1289. $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
  1290. }
  1291. }
  1292. }
  1293. }
  1294. $line->subprice = $pu_ht;
  1295. $line->tva_tx = $tva_tx;
  1296. $line->remise_percent = $remise_percent;
  1297. }
  1298. if ($update_desc === true) {
  1299. $line->desc = $prod->description;
  1300. }
  1301. }
  1302. }
  1303. }
  1304. }
  1305. }
  1306. $object->id = 0;
  1307. $object->ref = '';
  1308. $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
  1309. $object->statut = self::STATUS_DRAFT;
  1310. // Clear fields
  1311. $object->user_author = $user->id;
  1312. $object->user_validation_id = 0;
  1313. $object->date = $now;
  1314. $object->datep = $now; // deprecated
  1315. $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
  1316. if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) {
  1317. $object->ref_client = '';
  1318. }
  1319. if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
  1320. $object->note_private = '';
  1321. $object->note_public = '';
  1322. }
  1323. // Create clone
  1324. $object->context['createfromclone'] = 'createfromclone';
  1325. $result = $object->create($user);
  1326. if ($result < 0) {
  1327. $this->error = $object->error;
  1328. $this->errors = array_merge($this->errors, $object->errors);
  1329. $error++;
  1330. }
  1331. if (!$error) {
  1332. // copy internal contacts
  1333. if ($object->copy_linked_contact($this, 'internal') < 0) {
  1334. $error++;
  1335. }
  1336. }
  1337. if (!$error) {
  1338. // copy external contacts if same company
  1339. if ($this->socid == $object->socid) {
  1340. if ($object->copy_linked_contact($this, 'external') < 0) {
  1341. $error++;
  1342. }
  1343. }
  1344. }
  1345. if (!$error) {
  1346. // Hook of thirdparty module
  1347. if (is_object($hookmanager)) {
  1348. $parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
  1349. $action = '';
  1350. $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
  1351. if ($reshook < 0) {
  1352. $this->setErrorsFromObject($hookmanager);
  1353. $error++;
  1354. }
  1355. }
  1356. }
  1357. unset($object->context['createfromclone']);
  1358. // End
  1359. if (!$error) {
  1360. $this->db->commit();
  1361. return $object->id;
  1362. } else {
  1363. $this->db->rollback();
  1364. return -1;
  1365. }
  1366. }
  1367. /**
  1368. * Load a proposal from database. Get also lines.
  1369. *
  1370. * @param int $rowid Id of object to load
  1371. * @param string $ref Ref of proposal
  1372. * @param string $ref_ext Ref ext of proposal
  1373. * @param int $forceentity Entity id to force when searching on ref or ref_ext
  1374. * @return int >0 if OK, <0 if KO
  1375. */
  1376. public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
  1377. {
  1378. $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
  1379. $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
  1380. $sql .= ", p.datec";
  1381. $sql .= ", p.date_signature as dates";
  1382. $sql .= ", p.date_valid as datev";
  1383. $sql .= ", p.datep as dp";
  1384. $sql .= ", p.fin_validite as dfv";
  1385. $sql .= ", p.date_livraison as delivery_date";
  1386. $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
  1387. $sql .= ", p.note_private, p.note_public";
  1388. $sql .= ", p.fk_projet as fk_project, p.fk_statut";
  1389. $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
  1390. $sql .= ", p.fk_delivery_address";
  1391. $sql .= ", p.fk_availability";
  1392. $sql .= ", p.fk_input_reason";
  1393. $sql .= ", p.fk_cond_reglement";
  1394. $sql .= ", p.fk_mode_reglement";
  1395. $sql .= ', p.fk_account';
  1396. $sql .= ", p.fk_shipping_method";
  1397. $sql .= ", p.fk_warehouse";
  1398. $sql .= ", p.fk_incoterms, p.location_incoterms";
  1399. $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
  1400. $sql .= ", p.tms as date_modification";
  1401. $sql .= ", i.libelle as label_incoterms";
  1402. $sql .= ", c.label as statut_label";
  1403. $sql .= ", ca.code as availability_code, ca.label as availability";
  1404. $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
  1405. $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
  1406. $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
  1407. $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
  1408. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
  1409. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
  1410. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON p.fk_cond_reglement = cr.rowid AND cr.entity IN ('.getEntity('c_payment_term').')';
  1411. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
  1412. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
  1413. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
  1414. if (!empty($ref)) {
  1415. if (!empty($forceentity)) {
  1416. $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
  1417. } else {
  1418. $sql .= " WHERE p.entity IN (".getEntity('propal').")";
  1419. }
  1420. $sql .= " AND p.ref='".$this->db->escape($ref)."'";
  1421. } else {
  1422. // Dont't use entity if you use rowid
  1423. $sql .= " WHERE p.rowid = ".((int) $rowid);
  1424. }
  1425. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  1426. $resql = $this->db->query($sql);
  1427. if ($resql) {
  1428. if ($this->db->num_rows($resql)) {
  1429. $obj = $this->db->fetch_object($resql);
  1430. $this->id = $obj->rowid;
  1431. $this->entity = $obj->entity;
  1432. $this->ref = $obj->ref;
  1433. $this->ref_client = $obj->ref_client;
  1434. $this->ref_customer = $obj->ref_client;
  1435. $this->ref_ext = $obj->ref_ext;
  1436. $this->remise = $obj->remise; // TODO deprecated
  1437. $this->remise_percent = $obj->remise_percent; // TODO deprecated
  1438. $this->remise_absolue = $obj->remise_absolue; // TODO deprecated
  1439. $this->total = $obj->total_ttc; // TODO deprecated
  1440. $this->total_ttc = $obj->total_ttc;
  1441. $this->total_ht = $obj->total_ht;
  1442. $this->total_tva = $obj->total_tva;
  1443. $this->total_localtax1 = $obj->localtax1;
  1444. $this->total_localtax2 = $obj->localtax2;
  1445. $this->socid = $obj->fk_soc;
  1446. $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
  1447. $this->fk_project = $obj->fk_project;
  1448. $this->project = null; // Clear if another value was already set by fetch_projet
  1449. $this->model_pdf = $obj->model_pdf;
  1450. $this->last_main_doc = $obj->last_main_doc;
  1451. $this->note = $obj->note_private; // TODO deprecated
  1452. $this->note_private = $obj->note_private;
  1453. $this->note_public = $obj->note_public;
  1454. $this->status = (int) $obj->fk_statut;
  1455. $this->statut = $this->status; // deprecated
  1456. $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
  1457. $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
  1458. $this->date_creation = $this->db->jdate($obj->datec); //Creation date
  1459. $this->date_validation = $this->db->jdate($obj->datev); //Validation date
  1460. $this->date_modification = $this->db->jdate($obj->date_modification); // tms
  1461. $this->date_signature = $this->db->jdate($obj->dates); // Signature date
  1462. $this->date = $this->db->jdate($obj->dp); // Proposal date
  1463. $this->datep = $this->db->jdate($obj->dp); // deprecated
  1464. $this->fin_validite = $this->db->jdate($obj->dfv);
  1465. $this->delivery_date = $this->db->jdate($obj->delivery_date);
  1466. $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
  1467. $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
  1468. $this->availability_id = $obj->fk_availability;
  1469. $this->availability_code = $obj->availability_code;
  1470. $this->availability = $obj->availability;
  1471. $this->demand_reason_id = $obj->fk_input_reason;
  1472. $this->demand_reason_code = $obj->demand_reason_code;
  1473. $this->demand_reason = $obj->demand_reason;
  1474. $this->fk_address = $obj->fk_delivery_address;
  1475. $this->mode_reglement_id = $obj->fk_mode_reglement;
  1476. $this->mode_reglement_code = $obj->mode_reglement_code;
  1477. $this->mode_reglement = $obj->mode_reglement;
  1478. $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
  1479. $this->cond_reglement_id = $obj->fk_cond_reglement;
  1480. $this->cond_reglement_code = $obj->cond_reglement_code;
  1481. $this->cond_reglement = $obj->cond_reglement;
  1482. $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
  1483. $this->deposit_percent = $obj->deposit_percent;
  1484. $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
  1485. $this->user_author_id = $obj->fk_user_author;
  1486. $this->user_validation_id = $obj->fk_user_valid;
  1487. $this->user_closing_id = $obj->fk_user_cloture;
  1488. //Incoterms
  1489. $this->fk_incoterms = $obj->fk_incoterms;
  1490. $this->location_incoterms = $obj->location_incoterms;
  1491. $this->label_incoterms = $obj->label_incoterms;
  1492. // Multicurrency
  1493. $this->fk_multicurrency = $obj->fk_multicurrency;
  1494. $this->multicurrency_code = $obj->multicurrency_code;
  1495. $this->multicurrency_tx = $obj->multicurrency_tx;
  1496. $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
  1497. $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
  1498. $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
  1499. // Retrieve all extrafield
  1500. // fetch optionals attributes and labels
  1501. $this->fetch_optionals();
  1502. $this->db->free($resql);
  1503. $this->lines = array();
  1504. // Lines
  1505. $result = $this->fetch_lines();
  1506. if ($result < 0) {
  1507. return -3;
  1508. }
  1509. return 1;
  1510. }
  1511. $this->error = "Record Not Found";
  1512. return 0;
  1513. } else {
  1514. $this->error = $this->db->lasterror();
  1515. return -1;
  1516. }
  1517. }
  1518. /**
  1519. * Update database
  1520. *
  1521. * @param User $user User that modify
  1522. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  1523. * @return int <0 if KO, >0 if OK
  1524. */
  1525. public function update(User $user, $notrigger = 0)
  1526. {
  1527. global $conf;
  1528. $error = 0;
  1529. // Clean parameters
  1530. if (isset($this->ref)) {
  1531. $this->ref = trim($this->ref);
  1532. }
  1533. if (isset($this->ref_client)) {
  1534. $this->ref_client = trim($this->ref_client);
  1535. }
  1536. if (isset($this->note) || isset($this->note_private)) {
  1537. $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
  1538. }
  1539. if (isset($this->note_public)) {
  1540. $this->note_public = trim($this->note_public);
  1541. }
  1542. if (isset($this->model_pdf)) {
  1543. $this->model_pdf = trim($this->model_pdf);
  1544. }
  1545. if (isset($this->import_key)) {
  1546. $this->import_key = trim($this->import_key);
  1547. }
  1548. if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
  1549. $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
  1550. }
  1551. // Check parameters
  1552. // Put here code to add control on parameters values
  1553. // Update request
  1554. $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
  1555. $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
  1556. $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
  1557. $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
  1558. $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
  1559. $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
  1560. if (!empty($this->fin_validite)) {
  1561. $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
  1562. }
  1563. $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
  1564. $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
  1565. $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
  1566. $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
  1567. $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
  1568. $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
  1569. $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
  1570. $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
  1571. $sql .= " fk_user_valid=".(isset($this->user_validation_id) ? $this->user_validation_id : "null").",";
  1572. $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
  1573. $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
  1574. $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
  1575. $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
  1576. $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
  1577. $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
  1578. $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
  1579. $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
  1580. $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
  1581. $sql .= " WHERE rowid=".((int) $this->id);
  1582. $this->db->begin();
  1583. dol_syslog(get_class($this)."::update", LOG_DEBUG);
  1584. $resql = $this->db->query($sql);
  1585. if (!$resql) {
  1586. $error++;
  1587. $this->errors[] = "Error ".$this->db->lasterror();
  1588. }
  1589. if (!$error) {
  1590. $result = $this->insertExtraFields();
  1591. if ($result < 0) {
  1592. $error++;
  1593. }
  1594. }
  1595. if (!$error && !$notrigger) {
  1596. // Call trigger
  1597. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  1598. if ($result < 0) {
  1599. $error++;
  1600. }
  1601. // End call triggers
  1602. }
  1603. // Commit or rollback
  1604. if ($error) {
  1605. foreach ($this->errors as $errmsg) {
  1606. dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
  1607. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  1608. }
  1609. $this->db->rollback();
  1610. return -1 * $error;
  1611. } else {
  1612. $this->db->commit();
  1613. return 1;
  1614. }
  1615. }
  1616. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1617. /**
  1618. * Load array lines
  1619. *
  1620. * @param int $only_product Return only physical products
  1621. * @param int $loadalsotranslation Return translation for products
  1622. * @param string $sqlforgedfilters Filter on other fields
  1623. * @return int <0 if KO, >0 if OK
  1624. */
  1625. public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $sqlforgedfilters = '')
  1626. {
  1627. // phpcs:enable
  1628. $this->lines = array();
  1629. $sql = 'SELECT d.rowid, d.fk_propal, d.fk_parent_line, d.label as custom_label, d.description, d.price, d.vat_src_code, d.tva_tx, d.localtax1_tx, d.localtax2_tx, d.localtax1_type, d.localtax2_type, d.qty, d.fk_remise_except, d.remise_percent, d.subprice, d.fk_product,';
  1630. $sql .= ' d.info_bits, d.total_ht, d.total_tva, d.total_localtax1, d.total_localtax2, d.total_ttc, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht, d.special_code, d.rang, d.product_type,';
  1631. $sql .= ' d.fk_unit,';
  1632. $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tobatch as product_tobatch, p.barcode as product_barcode,';
  1633. $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
  1634. $sql .= ' d.date_start, d.date_end,';
  1635. $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
  1636. $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
  1637. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
  1638. $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
  1639. if ($only_product) {
  1640. $sql .= ' AND p.fk_product_type = 0';
  1641. }
  1642. if ($sqlforgedfilters) {
  1643. $sql .= $sqlforgedfilters;
  1644. }
  1645. $sql .= ' ORDER by d.rang';
  1646. dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
  1647. $result = $this->db->query($sql);
  1648. if ($result) {
  1649. require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
  1650. $num = $this->db->num_rows($result);
  1651. $i = 0;
  1652. while ($i < $num) {
  1653. $objp = $this->db->fetch_object($result);
  1654. $line = new PropaleLigne($this->db);
  1655. $line->rowid = $objp->rowid; //Deprecated
  1656. $line->id = $objp->rowid;
  1657. $line->fk_propal = $objp->fk_propal;
  1658. $line->fk_parent_line = $objp->fk_parent_line;
  1659. $line->product_type = $objp->product_type;
  1660. $line->label = $objp->custom_label;
  1661. $line->desc = $objp->description; // Description ligne
  1662. $line->description = $objp->description; // Description ligne
  1663. $line->qty = $objp->qty;
  1664. $line->vat_src_code = $objp->vat_src_code;
  1665. $line->tva_tx = $objp->tva_tx;
  1666. $line->localtax1_tx = $objp->localtax1_tx;
  1667. $line->localtax2_tx = $objp->localtax2_tx;
  1668. $line->localtax1_type = $objp->localtax1_type;
  1669. $line->localtax2_type = $objp->localtax2_type;
  1670. $line->subprice = $objp->subprice;
  1671. $line->fk_remise_except = $objp->fk_remise_except;
  1672. $line->remise_percent = $objp->remise_percent;
  1673. $line->price = $objp->price; // TODO deprecated
  1674. $line->info_bits = $objp->info_bits;
  1675. $line->total_ht = $objp->total_ht;
  1676. $line->total_tva = $objp->total_tva;
  1677. $line->total_localtax1 = $objp->total_localtax1;
  1678. $line->total_localtax2 = $objp->total_localtax2;
  1679. $line->total_ttc = $objp->total_ttc;
  1680. $line->fk_fournprice = $objp->fk_fournprice;
  1681. $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
  1682. $line->pa_ht = $marginInfos[0];
  1683. $line->marge_tx = $marginInfos[1];
  1684. $line->marque_tx = $marginInfos[2];
  1685. $line->special_code = $objp->special_code;
  1686. $line->rang = $objp->rang;
  1687. $line->fk_product = $objp->fk_product;
  1688. $line->ref = $objp->product_ref; // deprecated
  1689. $line->libelle = $objp->product_label; // deprecated
  1690. $line->product_ref = $objp->product_ref;
  1691. $line->product_label = $objp->product_label;
  1692. $line->product_desc = $objp->product_desc; // Description produit
  1693. $line->product_tobatch = $objp->product_tobatch;
  1694. $line->product_barcode = $objp->product_barcode;
  1695. $line->fk_product_type = $objp->fk_product_type; // deprecated
  1696. $line->fk_unit = $objp->fk_unit;
  1697. $line->weight = $objp->weight;
  1698. $line->weight_units = $objp->weight_units;
  1699. $line->volume = $objp->volume;
  1700. $line->volume_units = $objp->volume_units;
  1701. $line->date_start = $this->db->jdate($objp->date_start);
  1702. $line->date_end = $this->db->jdate($objp->date_end);
  1703. // Multicurrency
  1704. $line->fk_multicurrency = $objp->fk_multicurrency;
  1705. $line->multicurrency_code = $objp->multicurrency_code;
  1706. $line->multicurrency_subprice = $objp->multicurrency_subprice;
  1707. $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
  1708. $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
  1709. $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
  1710. $line->fetch_optionals();
  1711. // multilangs
  1712. if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
  1713. $tmpproduct = new Product($this->db);
  1714. $tmpproduct->fetch($objp->fk_product);
  1715. $tmpproduct->getMultiLangs();
  1716. $line->multilangs = $tmpproduct->multilangs;
  1717. }
  1718. $this->lines[$i] = $line;
  1719. $i++;
  1720. }
  1721. $this->db->free($result);
  1722. return $num;
  1723. } else {
  1724. $this->error = $this->db->lasterror();
  1725. return -3;
  1726. }
  1727. }
  1728. /**
  1729. * Set status to validated
  1730. *
  1731. * @param User $user Object user that validate
  1732. * @param int $notrigger 1=Does not execute triggers, 0=execute triggers
  1733. * @return int <0 if KO, 0=Nothing done, >=0 if OK
  1734. */
  1735. public function valid($user, $notrigger = 0)
  1736. {
  1737. global $conf;
  1738. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  1739. $error = 0;
  1740. // Protection
  1741. if ($this->statut == self::STATUS_VALIDATED) {
  1742. dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
  1743. return 0;
  1744. }
  1745. if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $user->hasRight('propal', 'creer'))
  1746. || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && $user->hasRight('propal', 'propal_advance', 'validate')))) {
  1747. $this->error = 'ErrorPermissionDenied';
  1748. dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
  1749. return -1;
  1750. }
  1751. $now = dol_now();
  1752. $this->db->begin();
  1753. // Numbering module definition
  1754. $soc = new Societe($this->db);
  1755. $soc->fetch($this->socid);
  1756. // Define new ref
  1757. if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
  1758. $num = $this->getNextNumRef($soc);
  1759. } else {
  1760. $num = $this->ref;
  1761. }
  1762. $this->newref = dol_sanitizeFileName($num);
  1763. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  1764. $sql .= " SET ref = '".$this->db->escape($num)."',";
  1765. $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
  1766. $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
  1767. dol_syslog(get_class($this)."::valid", LOG_DEBUG);
  1768. $resql = $this->db->query($sql);
  1769. if (!$resql) {
  1770. dol_print_error($this->db);
  1771. $error++;
  1772. }
  1773. // Trigger calls
  1774. if (!$error && !$notrigger) {
  1775. // Call trigger
  1776. $result = $this->call_trigger('PROPAL_VALIDATE', $user);
  1777. if ($result < 0) {
  1778. $error++;
  1779. }
  1780. // End call triggers
  1781. }
  1782. if (!$error) {
  1783. $this->oldref = $this->ref;
  1784. // Rename directory if dir was a temporary ref
  1785. if (preg_match('/^[\(]?PROV/i', $this->ref)) {
  1786. // Now we rename also files into index
  1787. $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'propale/".$this->db->escape($this->newref)."'";
  1788. $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
  1789. $resql = $this->db->query($sql);
  1790. if (!$resql) {
  1791. $error++;
  1792. $this->error = $this->db->lasterror();
  1793. }
  1794. // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
  1795. $oldref = dol_sanitizeFileName($this->ref);
  1796. $newref = dol_sanitizeFileName($num);
  1797. $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
  1798. $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
  1799. if (!$error && file_exists($dirsource)) {
  1800. dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
  1801. if (@rename($dirsource, $dirdest)) {
  1802. dol_syslog("Rename ok");
  1803. // Rename docs starting with $oldref with $newref
  1804. $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
  1805. foreach ($listoffiles as $fileentry) {
  1806. $dirsource = $fileentry['name'];
  1807. $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
  1808. $dirsource = $fileentry['path'].'/'.$dirsource;
  1809. $dirdest = $fileentry['path'].'/'.$dirdest;
  1810. @rename($dirsource, $dirdest);
  1811. }
  1812. }
  1813. }
  1814. }
  1815. $this->ref = $num;
  1816. $this->statut = self::STATUS_VALIDATED;
  1817. $this->status = self::STATUS_VALIDATED;
  1818. $this->user_validation_id = $user->id;
  1819. $this->datev = $now;
  1820. $this->date_validation = $now;
  1821. $this->db->commit();
  1822. return 1;
  1823. } else {
  1824. $this->db->rollback();
  1825. return -1;
  1826. }
  1827. }
  1828. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1829. /**
  1830. * Define proposal date
  1831. *
  1832. * @param User $user Object user that modify
  1833. * @param int $date Date
  1834. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  1835. * @return int <0 if KO, >0 if OK
  1836. */
  1837. public function set_date($user, $date, $notrigger = 0)
  1838. {
  1839. // phpcs:enable
  1840. if (empty($date)) {
  1841. $this->error = 'ErrorBadParameter';
  1842. dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
  1843. return -1;
  1844. }
  1845. if ($user->hasRight('propal', 'creer')) {
  1846. $error = 0;
  1847. $this->db->begin();
  1848. $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
  1849. $sql .= " WHERE rowid = ".((int) $this->id);
  1850. dol_syslog(__METHOD__, LOG_DEBUG);
  1851. $resql = $this->db->query($sql);
  1852. if (!$resql) {
  1853. $this->errors[] = $this->db->error();
  1854. $error++;
  1855. }
  1856. if (!$error) {
  1857. $this->oldcopy = clone $this;
  1858. $this->date = $date;
  1859. $this->datep = $date; // deprecated
  1860. }
  1861. if (!$notrigger && empty($error)) {
  1862. // Call trigger
  1863. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  1864. if ($result < 0) {
  1865. $error++;
  1866. }
  1867. // End call triggers
  1868. }
  1869. if (!$error) {
  1870. $this->db->commit();
  1871. return 1;
  1872. } else {
  1873. foreach ($this->errors as $errmsg) {
  1874. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  1875. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  1876. }
  1877. $this->db->rollback();
  1878. return -1 * $error;
  1879. }
  1880. }
  1881. return -1;
  1882. }
  1883. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1884. /**
  1885. * Define end validity date
  1886. *
  1887. * @param User $user Object user that modify
  1888. * @param int $date_end_validity End of validity date
  1889. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  1890. * @return int <0 if KO, >0 if OK
  1891. */
  1892. public function set_echeance($user, $date_end_validity, $notrigger = 0)
  1893. {
  1894. // phpcs:enable
  1895. if ($user->hasRight('propal', 'creer')) {
  1896. $error = 0;
  1897. $this->db->begin();
  1898. $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
  1899. $sql .= " WHERE rowid = ".((int) $this->id);
  1900. dol_syslog(__METHOD__, LOG_DEBUG);
  1901. $resql = $this->db->query($sql);
  1902. if (!$resql) {
  1903. $this->errors[] = $this->db->error();
  1904. $error++;
  1905. }
  1906. if (!$error) {
  1907. $this->oldcopy = clone $this;
  1908. $this->fin_validite = $date_end_validity;
  1909. }
  1910. if (!$notrigger && empty($error)) {
  1911. // Call trigger
  1912. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  1913. if ($result < 0) {
  1914. $error++;
  1915. }
  1916. // End call triggers
  1917. }
  1918. if (!$error) {
  1919. $this->db->commit();
  1920. return 1;
  1921. } else {
  1922. foreach ($this->errors as $errmsg) {
  1923. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  1924. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  1925. }
  1926. $this->db->rollback();
  1927. return -1 * $error;
  1928. }
  1929. }
  1930. return -1;
  1931. }
  1932. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1933. /**
  1934. * Set delivery date
  1935. *
  1936. * @param User $user Object user that modify
  1937. * @param int $delivery_date Delivery date
  1938. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  1939. * @return int <0 if ko, >0 if ok
  1940. * @deprecated Use setDeliveryDate
  1941. */
  1942. public function set_date_livraison($user, $delivery_date, $notrigger = 0)
  1943. {
  1944. // phpcs:enable
  1945. return $this->setDeliveryDate($user, $delivery_date, $notrigger);
  1946. }
  1947. /**
  1948. * Set delivery date
  1949. *
  1950. * @param User $user Object user that modify
  1951. * @param int $delivery_date Delivery date
  1952. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  1953. * @return int <0 if ko, >0 if ok
  1954. */
  1955. public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
  1956. {
  1957. if ($user->hasRight('propal', 'creer')) {
  1958. $error = 0;
  1959. $this->db->begin();
  1960. $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
  1961. $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
  1962. $sql .= " WHERE rowid = ".((int) $this->id);
  1963. dol_syslog(__METHOD__, LOG_DEBUG);
  1964. $resql = $this->db->query($sql);
  1965. if (!$resql) {
  1966. $this->errors[] = $this->db->error();
  1967. $error++;
  1968. }
  1969. if (!$error) {
  1970. $this->oldcopy = clone $this;
  1971. $this->delivery_date = $delivery_date;
  1972. }
  1973. if (!$notrigger && empty($error)) {
  1974. // Call trigger
  1975. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  1976. if ($result < 0) {
  1977. $error++;
  1978. }
  1979. // End call triggers
  1980. }
  1981. if (!$error) {
  1982. $this->db->commit();
  1983. return 1;
  1984. } else {
  1985. foreach ($this->errors as $errmsg) {
  1986. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  1987. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  1988. }
  1989. $this->db->rollback();
  1990. return -1 * $error;
  1991. }
  1992. }
  1993. return -1;
  1994. }
  1995. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1996. /**
  1997. * Set delivery
  1998. *
  1999. * @param User $user Object user that modify
  2000. * @param int $id Availability id
  2001. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2002. * @return int <0 if KO, >0 if OK
  2003. */
  2004. public function set_availability($user, $id, $notrigger = 0)
  2005. {
  2006. // phpcs:enable
  2007. if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
  2008. $error = 0;
  2009. $this->db->begin();
  2010. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  2011. $sql .= " SET fk_availability = ".((int) $id);
  2012. $sql .= " WHERE rowid = ".((int) $this->id);
  2013. dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
  2014. $resql = $this->db->query($sql);
  2015. if (!$resql) {
  2016. $this->errors[] = $this->db->error();
  2017. $error++;
  2018. }
  2019. if (!$error) {
  2020. $this->oldcopy = clone $this;
  2021. $this->fk_availability = $id;
  2022. $this->availability_id = $id;
  2023. }
  2024. if (!$notrigger && empty($error)) {
  2025. // Call trigger
  2026. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2027. if ($result < 0) {
  2028. $error++;
  2029. }
  2030. // End call triggers
  2031. }
  2032. if (!$error) {
  2033. $this->db->commit();
  2034. return 1;
  2035. } else {
  2036. foreach ($this->errors as $errmsg) {
  2037. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2038. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2039. }
  2040. $this->db->rollback();
  2041. return -1 * $error;
  2042. }
  2043. } else {
  2044. $error_str = 'Propal status do not meet requirement '.$this->statut;
  2045. dol_syslog(__METHOD__.$error_str, LOG_ERR);
  2046. $this->error = $error_str;
  2047. $this->errors[] = $this->error;
  2048. return -2;
  2049. }
  2050. }
  2051. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2052. /**
  2053. * Set source of demand
  2054. *
  2055. * @param User $user Object user that modify
  2056. * @param int $id Input reason id
  2057. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2058. * @return int <0 if KO, >0 if OK
  2059. */
  2060. public function set_demand_reason($user, $id, $notrigger = 0)
  2061. {
  2062. // phpcs:enable
  2063. if ($user->hasRight('propal', 'creer') && $this->statut >= self::STATUS_DRAFT) {
  2064. $error = 0;
  2065. $this->db->begin();
  2066. $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
  2067. $sql .= " SET fk_input_reason = ".((int) $id);
  2068. $sql .= " WHERE rowid = ".((int) $this->id);
  2069. dol_syslog(__METHOD__, LOG_DEBUG);
  2070. $resql = $this->db->query($sql);
  2071. if (!$resql) {
  2072. $this->errors[] = $this->db->error();
  2073. $error++;
  2074. }
  2075. if (!$error) {
  2076. $this->oldcopy = clone $this;
  2077. $this->demand_reason_id = $id;
  2078. }
  2079. if (!$notrigger && empty($error)) {
  2080. // Call trigger
  2081. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2082. if ($result < 0) {
  2083. $error++;
  2084. }
  2085. // End call triggers
  2086. }
  2087. if (!$error) {
  2088. $this->db->commit();
  2089. return 1;
  2090. } else {
  2091. foreach ($this->errors as $errmsg) {
  2092. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2093. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2094. }
  2095. $this->db->rollback();
  2096. return -1 * $error;
  2097. }
  2098. } else {
  2099. $error_str = 'Propal status do not meet requirement '.$this->statut;
  2100. dol_syslog(__METHOD__.$error_str, LOG_ERR);
  2101. $this->error = $error_str;
  2102. $this->errors[] = $this->error;
  2103. return -2;
  2104. }
  2105. }
  2106. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2107. /**
  2108. * Set customer reference number
  2109. *
  2110. * @param User $user Object user that modify
  2111. * @param string $ref_client Customer reference
  2112. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2113. * @return int <0 if ko, >0 if ok
  2114. */
  2115. public function set_ref_client($user, $ref_client, $notrigger = 0)
  2116. {
  2117. // phpcs:enable
  2118. if ($user->hasRight('propal', 'creer')) {
  2119. $error = 0;
  2120. $this->db->begin();
  2121. $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
  2122. $sql .= " WHERE rowid = ".((int) $this->id);
  2123. dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
  2124. $resql = $this->db->query($sql);
  2125. if (!$resql) {
  2126. $this->errors[] = $this->db->error();
  2127. $error++;
  2128. }
  2129. if (!$error) {
  2130. $this->oldcopy = clone $this;
  2131. $this->ref_client = $ref_client;
  2132. }
  2133. if (!$notrigger && empty($error)) {
  2134. // Call trigger
  2135. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2136. if ($result < 0) {
  2137. $error++;
  2138. }
  2139. // End call triggers
  2140. }
  2141. if (!$error) {
  2142. $this->db->commit();
  2143. return 1;
  2144. } else {
  2145. foreach ($this->errors as $errmsg) {
  2146. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2147. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2148. }
  2149. $this->db->rollback();
  2150. return -1 * $error;
  2151. }
  2152. }
  2153. return -1;
  2154. }
  2155. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2156. /**
  2157. * Set an overall discount on the proposal
  2158. *
  2159. * @param User $user Object user that modify
  2160. * @param double $remise Amount discount
  2161. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2162. * @return int <0 if ko, >0 if ok
  2163. * @deprecated remise_percent is a deprecated field for object parent
  2164. */
  2165. public function set_remise_percent($user, $remise, $notrigger = 0)
  2166. {
  2167. // phpcs:enable
  2168. $remise = trim($remise) ?trim($remise) : 0;
  2169. if ($user->hasRight('propal', 'creer')) {
  2170. $remise = price2num($remise, 2);
  2171. $error = 0;
  2172. $this->db->begin();
  2173. $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".((float) $remise);
  2174. $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
  2175. dol_syslog(__METHOD__, LOG_DEBUG);
  2176. $resql = $this->db->query($sql);
  2177. if (!$resql) {
  2178. $this->errors[] = $this->db->error();
  2179. $error++;
  2180. }
  2181. if (!$error) {
  2182. $this->oldcopy = clone $this;
  2183. $this->remise_percent = $remise;
  2184. $this->update_price(1);
  2185. }
  2186. if (!$notrigger && empty($error)) {
  2187. // Call trigger
  2188. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2189. if ($result < 0) {
  2190. $error++;
  2191. }
  2192. // End call triggers
  2193. }
  2194. if (!$error) {
  2195. $this->db->commit();
  2196. return 1;
  2197. } else {
  2198. foreach ($this->errors as $errmsg) {
  2199. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2200. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2201. }
  2202. $this->db->rollback();
  2203. return -1 * $error;
  2204. }
  2205. }
  2206. return -1;
  2207. }
  2208. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2209. /**
  2210. * Set an absolute overall discount on the proposal
  2211. *
  2212. * @param User $user Object user that modify
  2213. * @param double $remise Amount discount
  2214. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2215. * @return int <0 if ko, >0 if ok
  2216. */
  2217. public function set_remise_absolue($user, $remise, $notrigger = 0)
  2218. {
  2219. // phpcs:enable
  2220. if (empty($remise)) {
  2221. $remise = 0;
  2222. }
  2223. $remise = price2num($remise);
  2224. if ($user->hasRight('propal', 'creer')) {
  2225. $error = 0;
  2226. $this->db->begin();
  2227. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  2228. $sql .= " SET remise_absolue = ".((float) $remise);
  2229. $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
  2230. dol_syslog(__METHOD__, LOG_DEBUG);
  2231. $resql = $this->db->query($sql);
  2232. if (!$resql) {
  2233. $this->errors[] = $this->db->error();
  2234. $error++;
  2235. }
  2236. if (!$error) {
  2237. $this->oldcopy = clone $this;
  2238. $this->update_price(1);
  2239. }
  2240. if (!$notrigger && empty($error)) {
  2241. // Call trigger
  2242. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2243. if ($result < 0) {
  2244. $error++;
  2245. }
  2246. // End call triggers
  2247. }
  2248. if (!$error) {
  2249. $this->db->commit();
  2250. return 1;
  2251. } else {
  2252. foreach ($this->errors as $errmsg) {
  2253. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2254. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2255. }
  2256. $this->db->rollback();
  2257. return -1 * $error;
  2258. }
  2259. }
  2260. return -1;
  2261. }
  2262. /**
  2263. * Reopen the commercial proposal
  2264. *
  2265. * @param User $user Object user that close
  2266. * @param int $status Status
  2267. * @param string $note Comment
  2268. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2269. * @return int <0 if KO, >0 if OK
  2270. */
  2271. public function reopen($user, $status, $note = '', $notrigger = 0)
  2272. {
  2273. $error = 0;
  2274. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  2275. $sql .= " SET fk_statut = ".((int) $status).",";
  2276. if (!empty($note)) {
  2277. $sql .= " note_private = '".$this->db->escape($note)."',";
  2278. }
  2279. $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
  2280. $sql .= " WHERE rowid = ".((int) $this->id);
  2281. $this->db->begin();
  2282. dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
  2283. $resql = $this->db->query($sql);
  2284. if (!$resql) {
  2285. $error++;
  2286. $this->errors[] = "Error ".$this->db->lasterror();
  2287. }
  2288. if (!$error) {
  2289. if (!$notrigger) {
  2290. // Call trigger
  2291. $result = $this->call_trigger('PROPAL_REOPEN', $user);
  2292. if ($result < 0) {
  2293. $error++;
  2294. }
  2295. // End call triggers
  2296. }
  2297. }
  2298. // Commit or rollback
  2299. if ($error) {
  2300. if (!empty($this->errors)) {
  2301. foreach ($this->errors as $errmsg) {
  2302. dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
  2303. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2304. }
  2305. }
  2306. $this->db->rollback();
  2307. return -1 * $error;
  2308. } else {
  2309. $this->statut = $status;
  2310. $this->status = $status;
  2311. $this->db->commit();
  2312. return 1;
  2313. }
  2314. }
  2315. /**
  2316. * Close/set the commercial proposal to status signed or refused (fill also date signature)
  2317. *
  2318. * @param User $user Object user that close
  2319. * @param int $status Status (self::STATUS_BILLED or self::STATUS_REFUSED)
  2320. * @param string $note Complete private note with this note
  2321. * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
  2322. * @return int <0 if KO, >0 if OK
  2323. */
  2324. public function closeProposal($user, $status, $note = '', $notrigger = 0)
  2325. {
  2326. global $langs,$conf;
  2327. $error = 0;
  2328. $now = dol_now();
  2329. $this->db->begin();
  2330. $newprivatenote = dol_concatdesc($this->note_private, $note);
  2331. if (empty($conf->global->PROPALE_KEEP_OLD_SIGNATURE_INFO)) {
  2332. $date_signature = $now;
  2333. $fk_user_signature = $user->id;
  2334. } else {
  2335. $this->info($this->id);
  2336. if (!isset($this->date_signature) || $this->date_signature == '') {
  2337. $date_signature = $now;
  2338. $fk_user_signature = $user->id;
  2339. } else {
  2340. $date_signature = $this->date_signature;
  2341. $fk_user_signature = $this->user_signature->id;
  2342. }
  2343. }
  2344. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  2345. $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', date_signature='".$this->db->idate($date_signature)."', fk_user_signature=".$fk_user_signature;
  2346. $sql .= " WHERE rowid = ".((int) $this->id);
  2347. $resql = $this->db->query($sql);
  2348. if ($resql) {
  2349. // Status self::STATUS_REFUSED by default
  2350. $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
  2351. $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
  2352. if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
  2353. $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
  2354. $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
  2355. // The connected company is classified as a client
  2356. $soc=new Societe($this->db);
  2357. $soc->id = $this->socid;
  2358. $result = $soc->setAsCustomer();
  2359. if ($result < 0) {
  2360. $this->error=$this->db->lasterror();
  2361. $this->db->rollback();
  2362. return -2;
  2363. }
  2364. }
  2365. if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
  2366. // Define output language
  2367. $outputlangs = $langs;
  2368. if (getDolGlobalInt('MAIN_MULTILANGS')) {
  2369. $outputlangs = new Translate("", $conf);
  2370. $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
  2371. $outputlangs->setDefaultLang($newlang);
  2372. }
  2373. // PDF
  2374. $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
  2375. $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
  2376. $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
  2377. //$ret=$object->fetch($id); // Reload to get new records
  2378. $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
  2379. }
  2380. if (!$error) {
  2381. $this->oldcopy= clone $this;
  2382. $this->statut = $status;
  2383. $this->status = $status;
  2384. $this->date_signature = $date_signature;
  2385. $this->note_private = $newprivatenote;
  2386. }
  2387. if (!$notrigger && empty($error)) {
  2388. // Call trigger
  2389. $result=$this->call_trigger($trigger_name, $user);
  2390. if ($result < 0) {
  2391. $error++;
  2392. }
  2393. // End call triggers
  2394. }
  2395. if (!$error ) {
  2396. $this->db->commit();
  2397. return 1;
  2398. } else {
  2399. $this->statut = $this->oldcopy->statut;
  2400. $this->status = $this->oldcopy->statut;
  2401. $this->date_signature = $this->oldcopy->date_signature;
  2402. $this->note_private = $this->oldcopy->note_private;
  2403. $this->db->rollback();
  2404. return -1;
  2405. }
  2406. } else {
  2407. $this->error = $this->db->lasterror();
  2408. $this->db->rollback();
  2409. return -1;
  2410. }
  2411. }
  2412. /**
  2413. * Classify the proposal to status Billed
  2414. *
  2415. * @param User $user Object user
  2416. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2417. * @param string $note Complete private note with this note
  2418. * @return int <0 if KO, 0 = nothing done, >0 if OK
  2419. */
  2420. public function classifyBilled(User $user, $notrigger = 0, $note = '')
  2421. {
  2422. global $conf, $langs;
  2423. $error = 0;
  2424. $now = dol_now();
  2425. $num = 0;
  2426. $triggerName = 'PROPAL_CLASSIFY_BILLED';
  2427. $this->db->begin();
  2428. $newprivatenote = dol_concatdesc($this->note_private, $note);
  2429. $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
  2430. $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
  2431. $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
  2432. dol_syslog(__METHOD__, LOG_DEBUG);
  2433. $resql = $this->db->query($sql);
  2434. if (!$resql) {
  2435. $this->errors[] = $this->db->error();
  2436. $error++;
  2437. } else {
  2438. $num = $this->db->affected_rows($resql);
  2439. }
  2440. if (!$error) {
  2441. $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
  2442. if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
  2443. // Define output language
  2444. $outputlangs = $langs;
  2445. if (getDolGlobalInt('MAIN_MULTILANGS')) {
  2446. $outputlangs = new Translate("", $conf);
  2447. $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
  2448. $outputlangs->setDefaultLang($newlang);
  2449. }
  2450. // PDF
  2451. $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
  2452. $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
  2453. $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
  2454. //$ret=$object->fetch($id); // Reload to get new records
  2455. $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
  2456. }
  2457. $this->oldcopy = clone $this;
  2458. $this->statut = self::STATUS_BILLED;
  2459. $this->date_cloture = $now;
  2460. $this->note_private = $newprivatenote;
  2461. }
  2462. if (!$notrigger && empty($error)) {
  2463. // Call trigger
  2464. $result = $this->call_trigger($triggerName, $user);
  2465. if ($result < 0) {
  2466. $error++;
  2467. }
  2468. // End call triggers
  2469. }
  2470. if (!$error) {
  2471. $this->db->commit();
  2472. return $num;
  2473. } else {
  2474. foreach ($this->errors as $errmsg) {
  2475. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2476. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2477. }
  2478. $this->db->rollback();
  2479. return -1 * $error;
  2480. }
  2481. }
  2482. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2483. /**
  2484. * Set draft status
  2485. *
  2486. * @param User $user Object user that modify
  2487. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2488. * @return int <0 if KO, >0 if OK
  2489. */
  2490. public function setDraft($user, $notrigger = 0)
  2491. {
  2492. // phpcs:enable
  2493. $error = 0;
  2494. // Protection
  2495. if ($this->statut <= self::STATUS_DRAFT) {
  2496. return 0;
  2497. }
  2498. dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
  2499. $this->db->begin();
  2500. $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
  2501. $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
  2502. $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
  2503. $sql .= " WHERE rowid = ".((int) $this->id);
  2504. $resql = $this->db->query($sql);
  2505. if (!$resql) {
  2506. $this->errors[] = $this->db->error();
  2507. $error++;
  2508. }
  2509. if (!$error) {
  2510. $this->oldcopy = clone $this;
  2511. }
  2512. if (!$notrigger && empty($error)) {
  2513. // Call trigger
  2514. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2515. if ($result < 0) {
  2516. $error++;
  2517. }
  2518. // End call triggers
  2519. }
  2520. if (!$error) {
  2521. $this->statut = self::STATUS_DRAFT;
  2522. $this->status = self::STATUS_DRAFT;
  2523. $this->db->commit();
  2524. return 1;
  2525. } else {
  2526. foreach ($this->errors as $errmsg) {
  2527. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2528. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2529. }
  2530. $this->db->rollback();
  2531. return -1 * $error;
  2532. }
  2533. }
  2534. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2535. /**
  2536. * Return list of proposal (eventually filtered on user) into an array
  2537. *
  2538. * @param int $shortlist 0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
  2539. * @param int $draft 0=not draft, 1=draft
  2540. * @param int $notcurrentuser 0=all user, 1=not current user
  2541. * @param int $socid Id third pary
  2542. * @param int $limit For pagination
  2543. * @param int $offset For pagination
  2544. * @param string $sortfield Sort criteria
  2545. * @param string $sortorder Sort order
  2546. * @return array|int -1 if KO, array with result if OK
  2547. */
  2548. public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
  2549. {
  2550. // phpcs:enable
  2551. global $user;
  2552. $ga = array();
  2553. $sql = "SELECT s.rowid, s.nom as name, s.client,";
  2554. $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
  2555. $sql .= " p.datep as dp, p.fin_validite as datelimite";
  2556. if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
  2557. $sql .= ", sc.fk_soc, sc.fk_user";
  2558. }
  2559. $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
  2560. if (!$user->hasRight('societe', 'client', 'voir') && !$socid) {
  2561. $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
  2562. }
  2563. $sql .= " WHERE p.entity IN (".getEntity('propal').")";
  2564. $sql .= " AND p.fk_soc = s.rowid";
  2565. $sql .= " AND p.fk_statut = c.id";
  2566. if (!$user->hasRight('societe', 'client', 'voir') && !$socid) { //restriction
  2567. $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
  2568. }
  2569. if ($socid) {
  2570. $sql .= " AND s.rowid = ".((int) $socid);
  2571. }
  2572. if ($draft) {
  2573. $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
  2574. }
  2575. if ($notcurrentuser > 0) {
  2576. $sql .= " AND p.fk_user_author <> ".((int) $user->id);
  2577. }
  2578. $sql .= $this->db->order($sortfield, $sortorder);
  2579. $sql .= $this->db->plimit($limit, $offset);
  2580. $result = $this->db->query($sql);
  2581. if ($result) {
  2582. $num = $this->db->num_rows($result);
  2583. if ($num) {
  2584. $i = 0;
  2585. while ($i < $num) {
  2586. $obj = $this->db->fetch_object($result);
  2587. if ($shortlist == 1) {
  2588. $ga[$obj->propalid] = $obj->ref;
  2589. } elseif ($shortlist == 2) {
  2590. $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
  2591. } else {
  2592. $ga[$i]['id'] = $obj->propalid;
  2593. $ga[$i]['ref'] = $obj->ref;
  2594. $ga[$i]['name'] = $obj->name;
  2595. }
  2596. $i++;
  2597. }
  2598. }
  2599. return $ga;
  2600. } else {
  2601. dol_print_error($this->db);
  2602. return -1;
  2603. }
  2604. }
  2605. /**
  2606. * Returns an array with the numbers of related invoices
  2607. *
  2608. * @return array Array of invoices
  2609. */
  2610. public function getInvoiceArrayList()
  2611. {
  2612. return $this->InvoiceArrayList($this->id);
  2613. }
  2614. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2615. /**
  2616. * Returns an array with id and ref of related invoices
  2617. *
  2618. * @param int $id Id propal
  2619. * @return array|int Array of invoices id
  2620. */
  2621. public function InvoiceArrayList($id)
  2622. {
  2623. // phpcs:enable
  2624. $ga = array();
  2625. $linkedInvoices = array();
  2626. $this->fetchObjectLinked($id, $this->element);
  2627. foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
  2628. // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
  2629. // On parcourt donc une liste d'objets en tant qu'objet unique
  2630. foreach ($objectid as $key => $object) {
  2631. // Cas des factures liees directement
  2632. if ($objecttype == 'facture') {
  2633. $linkedInvoices[] = $object;
  2634. } else {
  2635. // Cas des factures liees par un autre objet (ex: commande)
  2636. $this->fetchObjectLinked($object, $objecttype);
  2637. foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
  2638. foreach ($subobjectid as $subkey => $subobject) {
  2639. if ($subobjecttype == 'facture') {
  2640. $linkedInvoices[] = $subobject;
  2641. }
  2642. }
  2643. }
  2644. }
  2645. }
  2646. }
  2647. if (count($linkedInvoices) > 0) {
  2648. $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
  2649. $sql .= " FROM ".MAIN_DB_PREFIX."facture";
  2650. $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
  2651. dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
  2652. $resql = $this->db->query($sql);
  2653. if ($resql) {
  2654. $tab_sqlobj = array();
  2655. $nump = $this->db->num_rows($resql);
  2656. for ($i = 0; $i < $nump; $i++) {
  2657. $sqlobj = $this->db->fetch_object($resql);
  2658. $tab_sqlobj[] = $sqlobj;
  2659. }
  2660. $this->db->free($resql);
  2661. $nump = count($tab_sqlobj);
  2662. if ($nump) {
  2663. $i = 0;
  2664. while ($i < $nump) {
  2665. $obj = array_shift($tab_sqlobj);
  2666. $ga[$i] = $obj;
  2667. $i++;
  2668. }
  2669. }
  2670. return $ga;
  2671. } else {
  2672. return -1;
  2673. }
  2674. } else {
  2675. return $ga;
  2676. }
  2677. }
  2678. /**
  2679. * Delete proposal
  2680. *
  2681. * @param User $user Object user that delete
  2682. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2683. * @return int >0 if OK, <=0 if KO
  2684. */
  2685. public function delete($user, $notrigger = 0)
  2686. {
  2687. global $conf;
  2688. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  2689. $error = 0;
  2690. $this->db->begin();
  2691. if (!$notrigger) {
  2692. // Call trigger
  2693. $result = $this->call_trigger('PROPAL_DELETE', $user);
  2694. if ($result < 0) {
  2695. $error++;
  2696. }
  2697. // End call triggers
  2698. }
  2699. // Delete extrafields of lines and lines
  2700. if (!$error && !empty($this->table_element_line)) {
  2701. $tabletodelete = $this->table_element_line;
  2702. $sqlef = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete."_extrafields WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id).")";
  2703. $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
  2704. if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
  2705. $error++;
  2706. $this->error = $this->db->lasterror();
  2707. $this->errors[] = $this->error;
  2708. dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
  2709. }
  2710. }
  2711. if (!$error) {
  2712. // Delete linked object
  2713. $res = $this->deleteObjectLinked();
  2714. if ($res < 0) {
  2715. $error++;
  2716. }
  2717. }
  2718. if (!$error) {
  2719. // Delete linked contacts
  2720. $res = $this->delete_linked_contact();
  2721. if ($res < 0) {
  2722. $error++;
  2723. }
  2724. }
  2725. // Removed extrafields of object
  2726. if (!$error) {
  2727. $result = $this->deleteExtraFields();
  2728. if ($result < 0) {
  2729. $error++;
  2730. dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
  2731. }
  2732. }
  2733. // Delete main record
  2734. if (!$error) {
  2735. $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
  2736. $res = $this->db->query($sql);
  2737. if (!$res) {
  2738. $error++;
  2739. $this->error = $this->db->lasterror();
  2740. $this->errors[] = $this->error;
  2741. dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
  2742. }
  2743. }
  2744. // Delete record into ECM index and physically
  2745. if (!$error) {
  2746. $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
  2747. if (!$res) {
  2748. $error++;
  2749. }
  2750. }
  2751. if (!$error) {
  2752. // We remove directory
  2753. $ref = dol_sanitizeFileName($this->ref);
  2754. if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
  2755. $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
  2756. $file = $dir."/".$ref.".pdf";
  2757. if (file_exists($file)) {
  2758. dol_delete_preview($this);
  2759. if (!dol_delete_file($file, 0, 0, 0, $this)) {
  2760. $this->error = 'ErrorFailToDeleteFile';
  2761. $this->errors[] = $this->error;
  2762. $this->db->rollback();
  2763. return 0;
  2764. }
  2765. }
  2766. if (file_exists($dir)) {
  2767. $res = @dol_delete_dir_recursive($dir);
  2768. if (!$res) {
  2769. $this->error = 'ErrorFailToDeleteDir';
  2770. $this->errors[] = $this->error;
  2771. $this->db->rollback();
  2772. return 0;
  2773. }
  2774. }
  2775. }
  2776. }
  2777. if (!$error) {
  2778. dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
  2779. $this->db->commit();
  2780. return 1;
  2781. } else {
  2782. $this->db->rollback();
  2783. return -1;
  2784. }
  2785. }
  2786. /**
  2787. * Change the delivery time
  2788. *
  2789. * @param int $availability_id Id of new delivery time
  2790. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2791. * @return int >0 if OK, <0 if KO
  2792. * @deprecated use set_availability
  2793. */
  2794. public function availability($availability_id, $notrigger = 0)
  2795. {
  2796. global $user;
  2797. if ($this->statut >= self::STATUS_DRAFT) {
  2798. $error = 0;
  2799. $this->db->begin();
  2800. $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
  2801. $sql .= ' SET fk_availability = '.((int) $availability_id);
  2802. $sql .= ' WHERE rowid='.((int) $this->id);
  2803. dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
  2804. $resql = $this->db->query($sql);
  2805. if (!$resql) {
  2806. $this->errors[] = $this->db->error();
  2807. $error++;
  2808. }
  2809. if (!$error) {
  2810. $this->oldcopy = clone $this;
  2811. $this->availability_id = $availability_id;
  2812. }
  2813. if (!$notrigger && empty($error)) {
  2814. // Call trigger
  2815. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2816. if ($result < 0) {
  2817. $error++;
  2818. }
  2819. // End call triggers
  2820. }
  2821. if (!$error) {
  2822. $this->db->commit();
  2823. return 1;
  2824. } else {
  2825. foreach ($this->errors as $errmsg) {
  2826. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2827. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2828. }
  2829. $this->db->rollback();
  2830. return -1 * $error;
  2831. }
  2832. } else {
  2833. $error_str = 'Propal status do not meet requirement '.$this->statut;
  2834. dol_syslog(__METHOD__.$error_str, LOG_ERR);
  2835. $this->error = $error_str;
  2836. $this->errors[] = $this->error;
  2837. return -2;
  2838. }
  2839. }
  2840. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2841. /**
  2842. * Change source demand
  2843. *
  2844. * @param int $demand_reason_id Id of new source demand
  2845. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  2846. * @return int >0 si ok, <0 si ko
  2847. * @deprecated use set_demand_reason
  2848. */
  2849. public function demand_reason($demand_reason_id, $notrigger = 0)
  2850. {
  2851. // phpcs:enable
  2852. global $user;
  2853. if ($this->status >= self::STATUS_DRAFT) {
  2854. $error = 0;
  2855. $this->db->begin();
  2856. $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
  2857. $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
  2858. $sql .= ' WHERE rowid='.((int) $this->id);
  2859. dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
  2860. $resql = $this->db->query($sql);
  2861. if (!$resql) {
  2862. $this->errors[] = $this->db->error();
  2863. $error++;
  2864. }
  2865. if (!$error) {
  2866. $this->oldcopy = clone $this;
  2867. $this->demand_reason_id = $demand_reason_id;
  2868. }
  2869. if (!$notrigger && empty($error)) {
  2870. // Call trigger
  2871. $result = $this->call_trigger('PROPAL_MODIFY', $user);
  2872. if ($result < 0) {
  2873. $error++;
  2874. }
  2875. // End call triggers
  2876. }
  2877. if (!$error) {
  2878. $this->db->commit();
  2879. return 1;
  2880. } else {
  2881. foreach ($this->errors as $errmsg) {
  2882. dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
  2883. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  2884. }
  2885. $this->db->rollback();
  2886. return -1 * $error;
  2887. }
  2888. } else {
  2889. $error_str = 'Propal status do not meet requirement '.$this->statut;
  2890. dol_syslog(__METHOD__.$error_str, LOG_ERR);
  2891. $this->error = $error_str;
  2892. $this->errors[] = $this->error;
  2893. return -2;
  2894. }
  2895. }
  2896. /**
  2897. * Object Proposal Information
  2898. *
  2899. * @param int $id Proposal id
  2900. * @return void
  2901. */
  2902. public function info($id)
  2903. {
  2904. $sql = "SELECT c.rowid, ";
  2905. $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
  2906. $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
  2907. $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
  2908. $sql .= " WHERE c.rowid = ".((int) $id);
  2909. $result = $this->db->query($sql);
  2910. if ($result) {
  2911. if ($this->db->num_rows($result)) {
  2912. $obj = $this->db->fetch_object($result);
  2913. $this->id = $obj->rowid;
  2914. $this->date_creation = $this->db->jdate($obj->datec);
  2915. $this->date_validation = $this->db->jdate($obj->datev);
  2916. $this->date_signature = $this->db->jdate($obj->date_signature);
  2917. $this->date_cloture = $this->db->jdate($obj->date_cloture);
  2918. $cuser = new User($this->db);
  2919. $cuser->fetch($obj->fk_user_author);
  2920. $this->user_creation = $cuser;
  2921. if ($obj->fk_user_valid) {
  2922. $this->user_validation_id = $obj->fk_user_valid;
  2923. }
  2924. if ($obj->fk_user_signature) {
  2925. $user_signature = new User($this->db);
  2926. $user_signature->fetch($obj->fk_user_signature);
  2927. $this->user_signature = $user_signature;
  2928. }
  2929. if ($obj->fk_user_cloture) {
  2930. $this->user_closing_id = $obj->fk_user_cloture;
  2931. }
  2932. }
  2933. $this->db->free($result);
  2934. } else {
  2935. dol_print_error($this->db);
  2936. }
  2937. }
  2938. /**
  2939. * Return label of status of proposal (draft, validated, ...)
  2940. *
  2941. * @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
  2942. * @return string Label
  2943. */
  2944. public function getLibStatut($mode = 0)
  2945. {
  2946. return $this->LibStatut($this->statut, $mode);
  2947. }
  2948. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2949. /**
  2950. * Return label of a status (draft, validated, ...)
  2951. *
  2952. * @param int $status Id status
  2953. * @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
  2954. * @return string Label
  2955. */
  2956. public function LibStatut($status, $mode = 1)
  2957. {
  2958. // phpcs:enable
  2959. global $conf, $hookmanager;
  2960. // Init/load array of translation of status
  2961. if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
  2962. global $langs;
  2963. $langs->load("propal");
  2964. $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
  2965. $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
  2966. $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
  2967. $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
  2968. $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
  2969. $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
  2970. $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
  2971. $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
  2972. $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
  2973. $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
  2974. }
  2975. $statusType = '';
  2976. if ($status == self::STATUS_DRAFT) {
  2977. $statusType = 'status0';
  2978. } elseif ($status == self::STATUS_VALIDATED) {
  2979. $statusType = 'status1';
  2980. } elseif ($status == self::STATUS_SIGNED) {
  2981. $statusType = 'status4';
  2982. } elseif ($status == self::STATUS_NOTSIGNED) {
  2983. $statusType = 'status9';
  2984. } elseif ($status == self::STATUS_BILLED) {
  2985. $statusType = 'status6';
  2986. }
  2987. $parameters = array('status' => $status, 'mode' => $mode);
  2988. $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
  2989. if ($reshook > 0) {
  2990. return $hookmanager->resPrint;
  2991. }
  2992. return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
  2993. }
  2994. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2995. /**
  2996. * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
  2997. *
  2998. * @param User $user Object user
  2999. * @param string $mode "opened" for proposal to close, "signed" for proposal to invoice
  3000. * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
  3001. */
  3002. public function load_board($user, $mode)
  3003. {
  3004. // phpcs:enable
  3005. global $conf, $langs;
  3006. $clause = " WHERE";
  3007. $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
  3008. $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
  3009. if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
  3010. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
  3011. $sql .= " WHERE sc.fk_user = ".((int) $user->id);
  3012. $clause = " AND";
  3013. }
  3014. $sql .= $clause." p.entity IN (".getEntity('propal').")";
  3015. if ($mode == 'opened') {
  3016. $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
  3017. }
  3018. if ($mode == 'signed') {
  3019. $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
  3020. }
  3021. if ($user->socid) {
  3022. $sql .= " AND p.fk_soc = ".((int) $user->socid);
  3023. }
  3024. $resql = $this->db->query($sql);
  3025. if ($resql) {
  3026. $langs->load("propal");
  3027. $now = dol_now();
  3028. $delay_warning = 0;
  3029. $status = 0;
  3030. $label = $labelShort = '';
  3031. if ($mode == 'opened') {
  3032. $delay_warning = $conf->propal->cloture->warning_delay;
  3033. $status = self::STATUS_VALIDATED;
  3034. $label = $langs->transnoentitiesnoconv("PropalsToClose");
  3035. $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
  3036. }
  3037. if ($mode == 'signed') {
  3038. $delay_warning = $conf->propal->facturation->warning_delay;
  3039. $status = self::STATUS_SIGNED;
  3040. $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
  3041. $labelShort = $langs->trans("ToBill");
  3042. }
  3043. $response = new WorkboardResponse();
  3044. $response->warning_delay = $delay_warning / 60 / 60 / 24;
  3045. $response->label = $label;
  3046. $response->labelShort = $labelShort;
  3047. $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
  3048. $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
  3049. $response->img = img_object('', "propal");
  3050. // This assignment in condition is not a bug. It allows walking the results.
  3051. while ($obj = $this->db->fetch_object($resql)) {
  3052. $response->nbtodo++;
  3053. $response->total += $obj->total_ht;
  3054. if ($mode == 'opened') {
  3055. $datelimit = $this->db->jdate($obj->datefin);
  3056. if ($datelimit < ($now - $delay_warning)) {
  3057. $response->nbtodolate++;
  3058. }
  3059. }
  3060. // TODO Definir regle des propales a facturer en retard
  3061. // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
  3062. }
  3063. return $response;
  3064. } else {
  3065. $this->error = $this->db->error();
  3066. return -1;
  3067. }
  3068. }
  3069. /**
  3070. * Initialise an instance with random values.
  3071. * Used to build previews or test instances.
  3072. * id must be 0 if object instance is a specimen.
  3073. *
  3074. * @return void
  3075. */
  3076. public function initAsSpecimen()
  3077. {
  3078. global $conf, $langs;
  3079. // Load array of products prodids
  3080. $num_prods = 0;
  3081. $prodids = array();
  3082. $sql = "SELECT rowid";
  3083. $sql .= " FROM ".MAIN_DB_PREFIX."product";
  3084. $sql .= " WHERE entity IN (".getEntity('product').")";
  3085. $sql .= $this->db->plimit(100);
  3086. $resql = $this->db->query($sql);
  3087. if ($resql) {
  3088. $num_prods = $this->db->num_rows($resql);
  3089. $i = 0;
  3090. while ($i < $num_prods) {
  3091. $i++;
  3092. $row = $this->db->fetch_row($resql);
  3093. $prodids[$i] = $row[0];
  3094. }
  3095. }
  3096. // Initialise parametres
  3097. $this->id = 0;
  3098. $this->ref = 'SPECIMEN';
  3099. $this->ref_client = 'NEMICEPS';
  3100. $this->specimen = 1;
  3101. $this->socid = 1;
  3102. $this->date = time();
  3103. $this->fin_validite = $this->date + 3600 * 24 * 30;
  3104. $this->cond_reglement_id = 1;
  3105. $this->cond_reglement_code = 'RECEP';
  3106. $this->mode_reglement_id = 7;
  3107. $this->mode_reglement_code = 'CHQ';
  3108. $this->availability_id = 1;
  3109. $this->availability_code = 'AV_NOW';
  3110. $this->demand_reason_id = 1;
  3111. $this->demand_reason_code = 'SRC_00';
  3112. $this->note_public = 'This is a comment (public)';
  3113. $this->note_private = 'This is a comment (private)';
  3114. $this->multicurrency_tx = 1;
  3115. $this->multicurrency_code = $conf->currency;
  3116. // Lines
  3117. $nbp = 5;
  3118. $xnbp = 0;
  3119. while ($xnbp < $nbp) {
  3120. $line = new PropaleLigne($this->db);
  3121. $line->desc = $langs->trans("Description")." ".$xnbp;
  3122. $line->qty = 1;
  3123. $line->subprice = 100;
  3124. $line->price = 100;
  3125. $line->tva_tx = 20;
  3126. $line->localtax1_tx = 0;
  3127. $line->localtax2_tx = 0;
  3128. if ($xnbp == 2) {
  3129. $line->total_ht = 50;
  3130. $line->total_ttc = 60;
  3131. $line->total_tva = 10;
  3132. $line->remise_percent = 50;
  3133. } else {
  3134. $line->total_ht = 100;
  3135. $line->total_ttc = 120;
  3136. $line->total_tva = 20;
  3137. $line->remise_percent = 00;
  3138. }
  3139. if ($num_prods > 0) {
  3140. $prodid = mt_rand(1, $num_prods);
  3141. $line->fk_product = $prodids[$prodid];
  3142. $line->product_ref = 'SPECIMEN';
  3143. }
  3144. $this->lines[$xnbp] = $line;
  3145. $this->total_ht += $line->total_ht;
  3146. $this->total_tva += $line->total_tva;
  3147. $this->total_ttc += $line->total_ttc;
  3148. $xnbp++;
  3149. }
  3150. }
  3151. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  3152. /**
  3153. * Charge indicateurs this->nb de tableau de bord
  3154. *
  3155. * @return int <0 if ko, >0 if ok
  3156. */
  3157. public function load_state_board()
  3158. {
  3159. // phpcs:enable
  3160. global $user;
  3161. $this->nb = array();
  3162. $clause = "WHERE";
  3163. $sql = "SELECT count(p.rowid) as nb";
  3164. $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
  3165. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
  3166. if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
  3167. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
  3168. $sql .= " WHERE sc.fk_user = ".((int) $user->id);
  3169. $clause = "AND";
  3170. }
  3171. $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
  3172. $resql = $this->db->query($sql);
  3173. if ($resql) {
  3174. // This assignment in condition is not a bug. It allows walking the results.
  3175. while ($obj = $this->db->fetch_object($resql)) {
  3176. $this->nb["proposals"] = $obj->nb;
  3177. }
  3178. $this->db->free($resql);
  3179. return 1;
  3180. } else {
  3181. dol_print_error($this->db);
  3182. $this->error = $this->db->error();
  3183. return -1;
  3184. }
  3185. }
  3186. /**
  3187. * Returns the reference to the following non used Proposal used depending on the active numbering module
  3188. * defined into PROPALE_ADDON
  3189. *
  3190. * @param Societe $soc Object thirdparty
  3191. * @return string Reference libre pour la propale
  3192. */
  3193. public function getNextNumRef($soc)
  3194. {
  3195. global $conf, $langs;
  3196. $langs->load("propal");
  3197. $classname = $conf->global->PROPALE_ADDON;
  3198. if (!empty($classname)) {
  3199. $mybool = false;
  3200. $file = $classname.".php";
  3201. // Include file with class
  3202. $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
  3203. foreach ($dirmodels as $reldir) {
  3204. $dir = dol_buildpath($reldir."core/modules/propale/");
  3205. // Load file with numbering class (if found)
  3206. $mybool |= @include_once $dir.$file;
  3207. }
  3208. if (!$mybool) {
  3209. dol_print_error('', "Failed to include file ".$file);
  3210. return '';
  3211. }
  3212. $obj = new $classname();
  3213. $numref = "";
  3214. $numref = $obj->getNextValue($soc, $this);
  3215. if ($numref != "") {
  3216. return $numref;
  3217. } else {
  3218. $this->error = $obj->error;
  3219. //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
  3220. return "";
  3221. }
  3222. } else {
  3223. $langs->load("errors");
  3224. print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
  3225. return "";
  3226. }
  3227. }
  3228. /**
  3229. * getTooltipContentArray
  3230. * @param array $params params to construct tooltip data
  3231. * @since v18
  3232. * @return array
  3233. */
  3234. public function getTooltipContentArray($params)
  3235. {
  3236. global $conf, $langs, $user;
  3237. $langs->load('propal');
  3238. $datas = [];
  3239. $nofetch = !empty($params['nofetch']);
  3240. if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
  3241. return ['optimize' => $langs->trans("Proposal")];
  3242. }
  3243. if ($user->hasRight('propal', 'lire')) {
  3244. $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
  3245. if (isset($this->statut)) {
  3246. $datas['status'] = ' '.$this->getLibStatut(5);
  3247. }
  3248. if (!empty($this->ref)) {
  3249. $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
  3250. }
  3251. if (!$nofetch) {
  3252. $langs->load('companies');
  3253. if (empty($this->thirdparty)) {
  3254. $this->fetch_thirdparty();
  3255. }
  3256. $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
  3257. }
  3258. if (!empty($this->ref_client)) {
  3259. $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
  3260. }
  3261. if (!$nofetch) {
  3262. $langs->load('project');
  3263. if (empty($this->project)) {
  3264. $res = $this->fetch_project();
  3265. if ($res > 0) {
  3266. $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
  3267. }
  3268. }
  3269. }
  3270. if (!empty($this->total_ht)) {
  3271. $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
  3272. }
  3273. if (!empty($this->total_tva)) {
  3274. $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
  3275. }
  3276. if (!empty($this->total_ttc)) {
  3277. $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
  3278. }
  3279. if (!empty($this->date)) {
  3280. $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
  3281. }
  3282. if (!empty($this->delivery_date)) {
  3283. $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
  3284. }
  3285. }
  3286. return $datas;
  3287. }
  3288. /**
  3289. * Return clicable link of object (with eventually picto)
  3290. *
  3291. * @param int $withpicto Add picto into link
  3292. * @param string $option Where point the link ('expedition', 'document', ...)
  3293. * @param string $get_params Parametres added to url
  3294. * @param int $notooltip 1=Disable tooltip
  3295. * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
  3296. * @param int $addlinktonotes -1=Disable, 0=Just add label show notes, 1=Add private note (only internal user), 2=Add public note (internal or external user), 3=Add private (internal user) and public note (internal and external user)
  3297. * @return string String with URL
  3298. */
  3299. public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
  3300. {
  3301. global $langs, $conf, $user, $hookmanager;
  3302. if (!empty($conf->dol_no_mouse_hover)) {
  3303. $notooltip = 1; // Force disable tooltips
  3304. }
  3305. $result = '';
  3306. $params = [
  3307. 'id' => $this->id,
  3308. 'objecttype' => $this->element,
  3309. 'option' => $option,
  3310. 'nofetch' => 1,
  3311. ];
  3312. $classfortooltip = 'classfortooltip';
  3313. $dataparams = '';
  3314. if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
  3315. $classfortooltip = 'classforajaxtooltip';
  3316. $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
  3317. $label = '';
  3318. } else {
  3319. $label = implode($this->getTooltipContentArray($params));
  3320. }
  3321. $url = '';
  3322. if ($user->hasRight('propal', 'lire')) {
  3323. if ($option == '') {
  3324. $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
  3325. } elseif ($option == 'compta') { // deprecated
  3326. $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
  3327. } elseif ($option == 'expedition') {
  3328. $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
  3329. } elseif ($option == 'document') {
  3330. $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
  3331. }
  3332. if ($option != 'nolink') {
  3333. // Add param to save lastsearch_values or not
  3334. $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
  3335. if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
  3336. $add_save_lastsearch_values = 1;
  3337. }
  3338. if ($add_save_lastsearch_values) {
  3339. $url .= '&save_lastsearch_values=1';
  3340. }
  3341. }
  3342. }
  3343. $linkclose = '';
  3344. if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
  3345. if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
  3346. $label = $langs->trans("Proposal");
  3347. $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
  3348. }
  3349. $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
  3350. $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
  3351. }
  3352. $linkstart = '<a href="'.$url.'"';
  3353. $linkstart .= $linkclose.'>';
  3354. $linkend = '</a>';
  3355. $result .= $linkstart;
  3356. if ($withpicto) {
  3357. $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
  3358. }
  3359. if ($withpicto != 2) {
  3360. $result .= $this->ref;
  3361. }
  3362. $result .= $linkend;
  3363. if ($addlinktonotes >= 0) {
  3364. $txttoshow = '';
  3365. if ($addlinktonotes == 0) {
  3366. if (!empty($this->note_private) || !empty($this->note_public)) {
  3367. $txttoshow = $langs->trans('ViewPrivateNote');
  3368. }
  3369. } elseif ($addlinktonotes == 1) {
  3370. if (!empty($this->note_private)) {
  3371. $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
  3372. }
  3373. } elseif ($addlinktonotes == 2) {
  3374. if (!empty($this->note_public)) {
  3375. $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
  3376. }
  3377. } elseif ($addlinktonotes == 3) {
  3378. if ($user->socid > 0) {
  3379. if (!empty($this->note_public)) {
  3380. $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
  3381. }
  3382. } else {
  3383. if (!empty($this->note_public)) {
  3384. $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
  3385. }
  3386. if (!empty($this->note_private)) {
  3387. if (!empty($txttoshow)) {
  3388. $txttoshow .= '<br><br>';
  3389. }
  3390. $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
  3391. }
  3392. }
  3393. }
  3394. if ($txttoshow) {
  3395. $result .= ' <span class="note inline-block">';
  3396. $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
  3397. $result .= img_picto('', 'note');
  3398. $result .= '</a>';
  3399. $result .= '</span>';
  3400. }
  3401. }
  3402. global $action;
  3403. $hookmanager->initHooks(array($this->element . 'dao'));
  3404. $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
  3405. $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  3406. if ($reshook > 0) {
  3407. $result = $hookmanager->resPrint;
  3408. } else {
  3409. $result .= $hookmanager->resPrint;
  3410. }
  3411. return $result;
  3412. }
  3413. /**
  3414. * Retrieve an array of proposal lines
  3415. *
  3416. * @param string $sqlforgedfilters Filter on other fields
  3417. * @return int >0 if OK, <0 if KO
  3418. */
  3419. public function getLinesArray($sqlforgedfilters = '')
  3420. {
  3421. return $this->fetch_lines(0, 0, $sqlforgedfilters);
  3422. }
  3423. /**
  3424. * Create a document onto disk according to template module.
  3425. *
  3426. * @param string $modele Force model to use ('' to not force)
  3427. * @param Translate $outputlangs Object langs to use for output
  3428. * @param int $hidedetails Hide details of lines
  3429. * @param int $hidedesc Hide description
  3430. * @param int $hideref Hide ref
  3431. * @param null|array $moreparams Array to provide more information
  3432. * @return int 0 if KO, 1 if OK
  3433. */
  3434. public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
  3435. {
  3436. global $conf, $langs;
  3437. $langs->load("propale");
  3438. $outputlangs->load("products");
  3439. if (!dol_strlen($modele)) {
  3440. $modele = 'azur';
  3441. if ($this->model_pdf) {
  3442. $modele = $this->model_pdf;
  3443. } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
  3444. $modele = $conf->global->PROPALE_ADDON_PDF;
  3445. }
  3446. }
  3447. $modelpath = "core/modules/propale/doc/";
  3448. return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
  3449. }
  3450. /**
  3451. * Function used to replace a thirdparty id with another one.
  3452. *
  3453. * @param DoliDB $dbs Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
  3454. * @param int $origin_id Old thirdparty id
  3455. * @param int $dest_id New thirdparty id
  3456. * @return bool
  3457. */
  3458. public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
  3459. {
  3460. $tables = array(
  3461. 'propal'
  3462. );
  3463. return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
  3464. }
  3465. /**
  3466. * Function used to replace a product id with another one.
  3467. *
  3468. * @param DoliDB $db Database handler
  3469. * @param int $origin_id Old product id
  3470. * @param int $dest_id New product id
  3471. * @return bool
  3472. */
  3473. public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
  3474. {
  3475. $tables = array(
  3476. 'propaldet'
  3477. );
  3478. return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
  3479. }
  3480. /**
  3481. * Return clicable link of object (with eventually picto)
  3482. *
  3483. * @param string $option Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
  3484. * @param array $arraydata Array of data
  3485. * @return string HTML Code for Kanban thumb.
  3486. */
  3487. public function getKanbanView($option = '', $arraydata = null)
  3488. {
  3489. global $langs;
  3490. $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
  3491. $return = '<div class="box-flex-item box-flex-grow-zero">';
  3492. $return .= '<div class="info-box info-box-sm">';
  3493. $return .= '<span class="info-box-icon bg-infobox-action">';
  3494. $return .= img_picto('', $this->picto);
  3495. $return .= '</span>';
  3496. $return .= '<div class="info-box-content">';
  3497. $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
  3498. $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
  3499. if (property_exists($this, 'fk_project')) {
  3500. $return .= '<span class="info-box-ref"> | '.$this->fk_project.'</span>';
  3501. }
  3502. if (property_exists($this, 'author')) {
  3503. $return .= '<br><span class="info-box-label">'.$this->author.'</span>';
  3504. }
  3505. if (property_exists($this, 'total_ht')) {
  3506. $return .='<br><span class="" >'.$langs->trans("AmountHT").' : </span><span class="info-box-label amount">'.price($this->total_ht).'</span>';
  3507. }
  3508. if (method_exists($this, 'getLibStatut')) {
  3509. $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
  3510. }
  3511. $return .= '</div>';
  3512. $return .= '</div>';
  3513. $return .= '</div>';
  3514. return $return;
  3515. }
  3516. }
  3517. /**
  3518. * Class to manage commercial proposal lines
  3519. */
  3520. class PropaleLigne extends CommonObjectLine
  3521. {
  3522. /**
  3523. * @var string ID to identify managed object
  3524. */
  3525. public $element = 'propaldet';
  3526. /**
  3527. * @var string Name of table without prefix where object is stored
  3528. */
  3529. public $table_element = 'propaldet';
  3530. public $oldline;
  3531. // From llx_propaldet
  3532. public $fk_propal;
  3533. public $fk_parent_line;
  3534. public $desc; // Description ligne
  3535. public $fk_product; // Id produit predefini
  3536. /**
  3537. * @deprecated
  3538. * @see $product_type
  3539. */
  3540. public $fk_product_type;
  3541. /**
  3542. * Product type.
  3543. * @var int
  3544. * @see Product::TYPE_PRODUCT, Product::TYPE_SERVICE
  3545. */
  3546. public $product_type = Product::TYPE_PRODUCT;
  3547. public $qty;
  3548. public $tva_tx;
  3549. public $vat_src_code;
  3550. public $subprice;
  3551. public $remise_percent;
  3552. public $fk_remise_except;
  3553. public $rang = 0;
  3554. public $fk_fournprice;
  3555. public $pa_ht;
  3556. public $marge_tx;
  3557. public $marque_tx;
  3558. public $special_code; // Tag for special lines (exlusive tags)
  3559. // 1: frais de port
  3560. // 2: ecotaxe
  3561. // 3: option line (when qty = 0)
  3562. public $info_bits = 0; // Some other info:
  3563. // Bit 0: 0 si TVA normal - 1 si TVA NPR
  3564. // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
  3565. public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
  3566. public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
  3567. public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
  3568. /**
  3569. * @deprecated
  3570. * @see $remise_percent, $fk_remise_except
  3571. */
  3572. public $remise;
  3573. /**
  3574. * @deprecated
  3575. * @see $subprice
  3576. */
  3577. public $price;
  3578. // From llx_product
  3579. /**
  3580. * @deprecated
  3581. * @see $product_ref
  3582. */
  3583. public $ref;
  3584. /**
  3585. * Product reference
  3586. * @var string
  3587. */
  3588. public $product_ref;
  3589. /**
  3590. * @deprecated
  3591. * @see $product_label
  3592. */
  3593. public $libelle;
  3594. /**
  3595. * @deprecated
  3596. * @see $product_label
  3597. */
  3598. public $label;
  3599. /**
  3600. * Product label
  3601. * @var string
  3602. */
  3603. public $product_label;
  3604. /**
  3605. * Product description
  3606. * @var string
  3607. */
  3608. public $product_desc;
  3609. /**
  3610. * Product use lot
  3611. * @var string
  3612. */
  3613. public $product_tobatch;
  3614. /**
  3615. * Product barcode
  3616. * @var string
  3617. */
  3618. public $product_barcode;
  3619. public $localtax1_tx; // Local tax 1
  3620. public $localtax2_tx; // Local tax 2
  3621. public $localtax1_type; // Local tax 1 type
  3622. public $localtax2_type; // Local tax 2 type
  3623. public $total_localtax1; // Line total local tax 1
  3624. public $total_localtax2; // Line total local tax 2
  3625. public $date_start;
  3626. public $date_end;
  3627. public $skip_update_total; // Skip update price total for special lines
  3628. // Multicurrency
  3629. public $fk_multicurrency;
  3630. public $multicurrency_code;
  3631. public $multicurrency_subprice;
  3632. public $multicurrency_total_ht;
  3633. public $multicurrency_total_tva;
  3634. public $multicurrency_total_ttc;
  3635. /**
  3636. * Class line Contructor
  3637. *
  3638. * @param DoliDB $db Database handler
  3639. */
  3640. public function __construct($db)
  3641. {
  3642. $this->db = $db;
  3643. }
  3644. /**
  3645. * Retrieve the propal line object
  3646. *
  3647. * @param int $rowid Propal line id
  3648. * @return int <0 if KO, >0 if OK
  3649. */
  3650. public function fetch($rowid)
  3651. {
  3652. $sql = 'SELECT pd.rowid, pd.fk_propal, pd.fk_parent_line, pd.fk_product, pd.label as custom_label, pd.description, pd.price, pd.qty, pd.vat_src_code, pd.tva_tx,';
  3653. $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
  3654. $sql .= ' pd.info_bits, pd.total_ht, pd.total_tva, pd.total_ttc, pd.fk_product_fournisseur_price as fk_fournprice, pd.buy_price_ht as pa_ht, pd.special_code, pd.rang,';
  3655. $sql .= ' pd.fk_unit,';
  3656. $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
  3657. $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
  3658. $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
  3659. $sql .= ' pd.date_start, pd.date_end, pd.product_type';
  3660. $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
  3661. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
  3662. $sql .= ' WHERE pd.rowid = '.((int) $rowid);
  3663. $result = $this->db->query($sql);
  3664. if ($result) {
  3665. $objp = $this->db->fetch_object($result);
  3666. if ($objp) {
  3667. $this->id = $objp->rowid;
  3668. $this->rowid = $objp->rowid; // deprecated
  3669. $this->fk_propal = $objp->fk_propal;
  3670. $this->fk_parent_line = $objp->fk_parent_line;
  3671. $this->label = $objp->custom_label;
  3672. $this->desc = $objp->description;
  3673. $this->qty = $objp->qty;
  3674. $this->price = $objp->price; // deprecated
  3675. $this->subprice = $objp->subprice;
  3676. $this->vat_src_code = $objp->vat_src_code;
  3677. $this->tva_tx = $objp->tva_tx;
  3678. $this->remise = $objp->remise; // deprecated
  3679. $this->remise_percent = $objp->remise_percent;
  3680. $this->fk_remise_except = $objp->fk_remise_except;
  3681. $this->fk_product = $objp->fk_product;
  3682. $this->info_bits = $objp->info_bits;
  3683. $this->total_ht = $objp->total_ht;
  3684. $this->total_tva = $objp->total_tva;
  3685. $this->total_ttc = $objp->total_ttc;
  3686. $this->fk_fournprice = $objp->fk_fournprice;
  3687. $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
  3688. $this->pa_ht = $marginInfos[0];
  3689. $this->marge_tx = $marginInfos[1];
  3690. $this->marque_tx = $marginInfos[2];
  3691. $this->special_code = $objp->special_code;
  3692. $this->product_type = $objp->product_type;
  3693. $this->rang = $objp->rang;
  3694. $this->ref = $objp->product_ref; // deprecated
  3695. $this->product_ref = $objp->product_ref;
  3696. $this->libelle = $objp->product_label; // deprecated
  3697. $this->product_label = $objp->product_label;
  3698. $this->product_desc = $objp->product_desc;
  3699. $this->fk_unit = $objp->fk_unit;
  3700. $this->date_start = $this->db->jdate($objp->date_start);
  3701. $this->date_end = $this->db->jdate($objp->date_end);
  3702. // Multicurrency
  3703. $this->fk_multicurrency = $objp->fk_multicurrency;
  3704. $this->multicurrency_code = $objp->multicurrency_code;
  3705. $this->multicurrency_subprice = $objp->multicurrency_subprice;
  3706. $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
  3707. $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
  3708. $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
  3709. $this->fetch_optionals();
  3710. $this->db->free($result);
  3711. return 1;
  3712. } else {
  3713. return 0;
  3714. }
  3715. } else {
  3716. return -1;
  3717. }
  3718. }
  3719. /**
  3720. * Insert object line propal in database
  3721. *
  3722. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  3723. * @return int <0 if KO, >0 if OK
  3724. */
  3725. public function insert($notrigger = 0)
  3726. {
  3727. global $conf, $user;
  3728. $error = 0;
  3729. dol_syslog(get_class($this)."::insert rang=".$this->rang);
  3730. $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
  3731. // Clean parameters
  3732. if (empty($this->tva_tx)) {
  3733. $this->tva_tx = 0;
  3734. }
  3735. if (empty($this->localtax1_tx)) {
  3736. $this->localtax1_tx = 0;
  3737. }
  3738. if (empty($this->localtax2_tx)) {
  3739. $this->localtax2_tx = 0;
  3740. }
  3741. if (empty($this->localtax1_type)) {
  3742. $this->localtax1_type = 0;
  3743. }
  3744. if (empty($this->localtax2_type)) {
  3745. $this->localtax2_type = 0;
  3746. }
  3747. if (empty($this->total_localtax1)) {
  3748. $this->total_localtax1 = 0;
  3749. }
  3750. if (empty($this->total_localtax2)) {
  3751. $this->total_localtax2 = 0;
  3752. }
  3753. if (empty($this->rang)) {
  3754. $this->rang = 0;
  3755. }
  3756. if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
  3757. $this->remise_percent = 0;
  3758. }
  3759. if (empty($this->info_bits)) {
  3760. $this->info_bits = 0;
  3761. }
  3762. if (empty($this->special_code)) {
  3763. $this->special_code = 0;
  3764. }
  3765. if (empty($this->fk_parent_line)) {
  3766. $this->fk_parent_line = 0;
  3767. }
  3768. if (empty($this->fk_fournprice)) {
  3769. $this->fk_fournprice = 0;
  3770. }
  3771. if (!is_numeric($this->qty)) {
  3772. $this->qty = 0;
  3773. }
  3774. if (empty($this->pa_ht)) {
  3775. $this->pa_ht = 0;
  3776. }
  3777. if (empty($this->multicurrency_subprice)) {
  3778. $this->multicurrency_subprice = 0;
  3779. }
  3780. if (empty($this->multicurrency_total_ht)) {
  3781. $this->multicurrency_total_ht = 0;
  3782. }
  3783. if (empty($this->multicurrency_total_tva)) {
  3784. $this->multicurrency_total_tva = 0;
  3785. }
  3786. if (empty($this->multicurrency_total_ttc)) {
  3787. $this->multicurrency_total_ttc = 0;
  3788. }
  3789. // if buy price not defined, define buyprice as configured in margin admin
  3790. if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
  3791. if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
  3792. return $result;
  3793. } else {
  3794. $this->pa_ht = $result;
  3795. }
  3796. }
  3797. // Check parameters
  3798. if ($this->product_type < 0) {
  3799. return -1;
  3800. }
  3801. $this->db->begin();
  3802. // Insert line into database
  3803. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
  3804. $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
  3805. $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
  3806. $sql .= ' subprice, remise_percent, ';
  3807. $sql .= ' info_bits, ';
  3808. $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
  3809. $sql .= ' fk_unit,';
  3810. $sql .= ' date_start, date_end';
  3811. $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
  3812. $sql .= " VALUES (".$this->fk_propal.",";
  3813. $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
  3814. $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
  3815. $sql .= " '".$this->db->escape($this->desc)."',";
  3816. $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
  3817. $sql .= " '".$this->db->escape($this->product_type)."',";
  3818. $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
  3819. $sql .= " ".price2num($this->qty, 'MS').",";
  3820. $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
  3821. $sql .= " ".price2num($this->tva_tx).",";
  3822. $sql .= " ".price2num($this->localtax1_tx).",";
  3823. $sql .= " ".price2num($this->localtax2_tx).",";
  3824. $sql .= " '".$this->db->escape($this->localtax1_type)."',";
  3825. $sql .= " '".$this->db->escape($this->localtax2_type)."',";
  3826. $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
  3827. $sql .= " ".price2num($this->remise_percent, 3).",";
  3828. $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
  3829. $sql .= " ".price2num($this->total_ht, 'MT').",";
  3830. $sql .= " ".price2num($this->total_tva, 'MT').",";
  3831. $sql .= " ".price2num($this->total_localtax1, 'MT').",";
  3832. $sql .= " ".price2num($this->total_localtax2, 'MT').",";
  3833. $sql .= " ".price2num($this->total_ttc, 'MT').",";
  3834. $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
  3835. $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
  3836. $sql .= ' '.((int) $this->special_code).',';
  3837. $sql .= ' '.((int) $this->rang).',';
  3838. $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
  3839. $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
  3840. $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
  3841. $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
  3842. $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
  3843. $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
  3844. $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
  3845. $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
  3846. $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
  3847. $sql .= ')';
  3848. dol_syslog(get_class($this).'::insert', LOG_DEBUG);
  3849. $resql = $this->db->query($sql);
  3850. if ($resql) {
  3851. $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
  3852. if (!$error) {
  3853. $this->id = $this->rowid;
  3854. $result = $this->insertExtraFields();
  3855. if ($result < 0) {
  3856. $error++;
  3857. }
  3858. }
  3859. if (!$error && !$notrigger) {
  3860. // Call trigger
  3861. $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
  3862. if ($result < 0) {
  3863. $this->db->rollback();
  3864. return -1;
  3865. }
  3866. // End call triggers
  3867. }
  3868. $this->db->commit();
  3869. return 1;
  3870. } else {
  3871. $this->error = $this->db->error()." sql=".$sql;
  3872. $this->db->rollback();
  3873. return -1;
  3874. }
  3875. }
  3876. /**
  3877. * Delete line in database
  3878. *
  3879. * @param User $user Object user
  3880. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  3881. * @return int <0 if ko, >0 if ok
  3882. */
  3883. public function delete(User $user, $notrigger = 0)
  3884. {
  3885. global $conf;
  3886. $error = 0;
  3887. $this->db->begin();
  3888. if (!$notrigger) {
  3889. // Call trigger
  3890. $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
  3891. if ($result < 0) {
  3892. $error++;
  3893. }
  3894. }
  3895. // End call triggers
  3896. if (!$error) {
  3897. $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
  3898. dol_syslog("PropaleLigne::delete", LOG_DEBUG);
  3899. if ($this->db->query($sql)) {
  3900. // Remove extrafields
  3901. if (!$error) {
  3902. $this->id = $this->rowid;
  3903. $result = $this->deleteExtraFields();
  3904. if ($result < 0) {
  3905. $error++;
  3906. dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
  3907. }
  3908. }
  3909. } else {
  3910. $this->error = $this->db->error() . " sql=" . $sql;
  3911. $error++;
  3912. }
  3913. }
  3914. if ($error) {
  3915. $this->db->rollback();
  3916. return -1;
  3917. } else {
  3918. $this->db->commit();
  3919. return 1;
  3920. }
  3921. }
  3922. /**
  3923. * Update propal line object into DB
  3924. *
  3925. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  3926. * @return int <0 if ko, >0 if ok
  3927. */
  3928. public function update($notrigger = 0)
  3929. {
  3930. global $conf, $user;
  3931. $error = 0;
  3932. $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
  3933. if (empty($this->id) && !empty($this->rowid)) {
  3934. $this->id = $this->rowid;
  3935. }
  3936. // Clean parameters
  3937. if (empty($this->tva_tx)) {
  3938. $this->tva_tx = 0;
  3939. }
  3940. if (empty($this->localtax1_tx)) {
  3941. $this->localtax1_tx = 0;
  3942. }
  3943. if (empty($this->localtax2_tx)) {
  3944. $this->localtax2_tx = 0;
  3945. }
  3946. if (empty($this->total_localtax1)) {
  3947. $this->total_localtax1 = 0;
  3948. }
  3949. if (empty($this->total_localtax2)) {
  3950. $this->total_localtax2 = 0;
  3951. }
  3952. if (empty($this->localtax1_type)) {
  3953. $this->localtax1_type = 0;
  3954. }
  3955. if (empty($this->localtax2_type)) {
  3956. $this->localtax2_type = 0;
  3957. }
  3958. if (empty($this->marque_tx)) {
  3959. $this->marque_tx = 0;
  3960. }
  3961. if (empty($this->marge_tx)) {
  3962. $this->marge_tx = 0;
  3963. }
  3964. if (empty($this->price)) {
  3965. $this->price = 0; // TODO A virer
  3966. }
  3967. if (empty($this->remise_percent)) {
  3968. $this->remise_percent = 0;
  3969. }
  3970. if (empty($this->info_bits)) {
  3971. $this->info_bits = 0;
  3972. }
  3973. if (empty($this->special_code)) {
  3974. $this->special_code = 0;
  3975. }
  3976. if (empty($this->fk_parent_line)) {
  3977. $this->fk_parent_line = 0;
  3978. }
  3979. if (empty($this->fk_fournprice)) {
  3980. $this->fk_fournprice = 0;
  3981. }
  3982. if (empty($this->subprice)) {
  3983. $this->subprice = 0;
  3984. }
  3985. if (empty($this->pa_ht)) {
  3986. $this->pa_ht = 0;
  3987. }
  3988. // if buy price not defined, define buyprice as configured in margin admin
  3989. if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
  3990. if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
  3991. return $result;
  3992. } else {
  3993. $this->pa_ht = $result;
  3994. }
  3995. }
  3996. $this->db->begin();
  3997. // Mise a jour ligne en base
  3998. $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
  3999. $sql .= " description='".$this->db->escape($this->desc)."'";
  4000. $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
  4001. $sql .= ", product_type=".$this->product_type;
  4002. $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
  4003. $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
  4004. $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
  4005. $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
  4006. $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
  4007. $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
  4008. $sql .= ", qty='".price2num($this->qty)."'";
  4009. $sql .= ", subprice=".price2num($this->subprice);
  4010. $sql .= ", remise_percent=".price2num($this->remise_percent);
  4011. $sql .= ", price=".(float) price2num($this->price); // TODO A virer
  4012. $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
  4013. $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
  4014. if (empty($this->skip_update_total)) {
  4015. $sql .= ", total_ht=".price2num($this->total_ht);
  4016. $sql .= ", total_tva=".price2num($this->total_tva);
  4017. $sql .= ", total_ttc=".price2num($this->total_ttc);
  4018. $sql .= ", total_localtax1=".price2num($this->total_localtax1);
  4019. $sql .= ", total_localtax2=".price2num($this->total_localtax2);
  4020. }
  4021. $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
  4022. $sql .= ", buy_price_ht=".price2num($this->pa_ht);
  4023. if (strlen($this->special_code)) {
  4024. $sql .= ", special_code=".$this->special_code;
  4025. }
  4026. $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
  4027. if (!empty($this->rang)) {
  4028. $sql .= ", rang=".((int) $this->rang);
  4029. }
  4030. $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
  4031. $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
  4032. $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
  4033. // Multicurrency
  4034. $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
  4035. $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
  4036. $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
  4037. $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
  4038. $sql .= " WHERE rowid = ".((int) $this->id);
  4039. dol_syslog(get_class($this)."::update", LOG_DEBUG);
  4040. $resql = $this->db->query($sql);
  4041. if ($resql) {
  4042. if (!$error) {
  4043. $result = $this->insertExtraFields();
  4044. if ($result < 0) {
  4045. $error++;
  4046. }
  4047. }
  4048. if (!$error && !$notrigger) {
  4049. // Call trigger
  4050. $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
  4051. if ($result < 0) {
  4052. $this->db->rollback();
  4053. return -1;
  4054. }
  4055. // End call triggers
  4056. }
  4057. $this->db->commit();
  4058. return 1;
  4059. } else {
  4060. $this->error = $this->db->error();
  4061. $this->db->rollback();
  4062. return -2;
  4063. }
  4064. }
  4065. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  4066. /**
  4067. * Update DB line fields total_xxx
  4068. * Used by migration
  4069. *
  4070. * @return int <0 if KO, >0 if OK
  4071. */
  4072. public function update_total()
  4073. {
  4074. // phpcs:enable
  4075. $this->db->begin();
  4076. // Mise a jour ligne en base
  4077. $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
  4078. $sql .= " total_ht=".price2num($this->total_ht, 'MT');
  4079. $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
  4080. $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
  4081. $sql .= " WHERE rowid = ".((int) $this->rowid);
  4082. dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
  4083. $resql = $this->db->query($sql);
  4084. if ($resql) {
  4085. $this->db->commit();
  4086. return 1;
  4087. } else {
  4088. $this->error = $this->db->error();
  4089. $this->db->rollback();
  4090. return -2;
  4091. }
  4092. }
  4093. }