test_move.py 278 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo import Command
  4. from odoo.exceptions import UserError
  5. from odoo.tests import Form
  6. from odoo.tests.common import TransactionCase
  7. class StockMove(TransactionCase):
  8. @classmethod
  9. def setUpClass(cls):
  10. super(StockMove, cls).setUpClass()
  11. group_stock_multi_locations = cls.env.ref('stock.group_stock_multi_locations')
  12. cls.env.user.write({'groups_id': [(4, group_stock_multi_locations.id, 0)]})
  13. cls.stock_location = cls.env.ref('stock.stock_location_stock')
  14. cls.customer_location = cls.env.ref('stock.stock_location_customers')
  15. cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
  16. cls.pack_location = cls.env.ref('stock.location_pack_zone')
  17. cls.pack_location.active = True
  18. cls.transit_location = cls.env['stock.location'].search([
  19. ('company_id', '=', cls.env.company.id),
  20. ('usage', '=', 'transit'),
  21. ('active', '=', False)
  22. ], limit=1)
  23. cls.transit_location.active = True
  24. cls.uom_unit = cls.env.ref('uom.product_uom_unit')
  25. cls.uom_dozen = cls.env.ref('uom.product_uom_dozen')
  26. cls.product = cls.env['product.product'].create({
  27. 'name': 'Product A',
  28. 'type': 'product',
  29. 'categ_id': cls.env.ref('product.product_category_all').id,
  30. })
  31. cls.product_serial = cls.env['product.product'].create({
  32. 'name': 'Product A',
  33. 'type': 'product',
  34. 'tracking': 'serial',
  35. 'categ_id': cls.env.ref('product.product_category_all').id,
  36. })
  37. cls.product_lot = cls.env['product.product'].create({
  38. 'name': 'Product A',
  39. 'type': 'product',
  40. 'tracking': 'lot',
  41. 'categ_id': cls.env.ref('product.product_category_all').id,
  42. })
  43. cls.product_consu = cls.env['product.product'].create({
  44. 'name': 'Product A',
  45. 'type': 'consu',
  46. 'categ_id': cls.env.ref('product.product_category_all').id,
  47. })
  48. def gather_relevant(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False):
  49. quants = self.env['stock.quant']._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
  50. return quants.filtered(lambda q: not (q.quantity == 0 and q.reserved_quantity == 0))
  51. def test_in_1(self):
  52. """ Receive products from a supplier. Check that a move line is created and that the
  53. reception correctly increase a single quant in stock.
  54. """
  55. # creation
  56. move1 = self.env['stock.move'].create({
  57. 'name': 'test_in_1',
  58. 'location_id': self.supplier_location.id,
  59. 'location_dest_id': self.stock_location.id,
  60. 'product_id': self.product.id,
  61. 'product_uom': self.uom_unit.id,
  62. 'product_uom_qty': 100.0,
  63. })
  64. self.assertEqual(move1.state, 'draft')
  65. # confirmation
  66. move1._action_confirm()
  67. self.assertEqual(move1.state, 'assigned')
  68. self.assertEqual(len(move1.move_line_ids), 1)
  69. # fill the move line
  70. move_line = move1.move_line_ids[0]
  71. self.assertEqual(move_line.reserved_qty, 100.0)
  72. self.assertEqual(move_line.qty_done, 0.0)
  73. move_line.qty_done = 100.0
  74. # validation
  75. move1._action_done()
  76. self.assertEqual(move1.state, 'done')
  77. # no quants are created in the supplier location
  78. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.supplier_location), 0.0)
  79. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.supplier_location, allow_negative=True), -100.0)
  80. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 100.0)
  81. self.assertEqual(len(self.gather_relevant(self.product, self.supplier_location)), 1.0)
  82. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  83. def test_in_2(self):
  84. """ Receive 5 tracked products from a supplier. The create move line should have 5
  85. reserved. If i assign the 5 items to lot1, the reservation should not change. Once
  86. i validate, the reception correctly increase a single quant in stock.
  87. """
  88. # creation
  89. move1 = self.env['stock.move'].create({
  90. 'name': 'test_in_1',
  91. 'location_id': self.supplier_location.id,
  92. 'location_dest_id': self.stock_location.id,
  93. 'product_id': self.product_lot.id,
  94. 'product_uom': self.uom_unit.id,
  95. 'product_uom_qty': 5.0,
  96. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  97. })
  98. self.assertEqual(move1.state, 'draft')
  99. # confirmation
  100. move1._action_confirm()
  101. self.assertEqual(move1.state, 'assigned')
  102. self.assertEqual(len(move1.move_line_ids), 1)
  103. move_line = move1.move_line_ids[0]
  104. self.assertEqual(move_line.reserved_qty, 5)
  105. move_line.lot_name = 'lot1'
  106. move_line.qty_done = 5.0
  107. self.assertEqual(move_line.reserved_qty, 5) # don't change reservation
  108. move1._action_done()
  109. self.assertEqual(move_line.reserved_qty, 0) # change reservation to 0 for done move
  110. self.assertEqual(move1.state, 'done')
  111. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.supplier_location), 0.0)
  112. supplier_quants = self.gather_relevant(self.product_lot, self.supplier_location)
  113. self.assertEqual(sum(supplier_quants.mapped('quantity')), -5.0)
  114. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location), 5.0)
  115. self.assertEqual(len(self.gather_relevant(self.product_lot, self.supplier_location)), 1.0)
  116. quants = self.gather_relevant(self.product_lot, self.stock_location)
  117. self.assertEqual(len(quants), 1.0)
  118. for quant in quants:
  119. self.assertNotEqual(quant.in_date, False)
  120. def test_in_3(self):
  121. """ Receive 5 serial-tracked products from a supplier. The system should create 5 different
  122. move line.
  123. """
  124. # creation
  125. move1 = self.env['stock.move'].create({
  126. 'name': 'test_in_1',
  127. 'location_id': self.supplier_location.id,
  128. 'location_dest_id': self.stock_location.id,
  129. 'product_id': self.product_serial.id,
  130. 'product_uom': self.uom_unit.id,
  131. 'product_uom_qty': 5.0,
  132. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  133. })
  134. self.assertEqual(move1.state, 'draft')
  135. # confirmation
  136. move1._action_confirm()
  137. self.assertEqual(move1.state, 'assigned')
  138. self.assertEqual(len(move1.move_line_ids), 5)
  139. move_line = move1.move_line_ids[0]
  140. self.assertEqual(move1.reserved_availability, 5)
  141. i = 0
  142. for move_line in move1.move_line_ids:
  143. move_line.lot_name = 'sn%s' % i
  144. move_line.qty_done = 1
  145. i += 1
  146. self.assertEqual(move1.quantity_done, 5.0)
  147. self.assertEqual(move1.product_qty, 5) # don't change reservation
  148. move1._action_done()
  149. self.assertEqual(move1.quantity_done, 5.0)
  150. self.assertEqual(move1.product_qty, 5) # don't change reservation
  151. self.assertEqual(move1.state, 'done')
  152. # Quant balance should result with 5 quant in supplier and stock
  153. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.supplier_location), 0.0)
  154. supplier_quants = self.gather_relevant(self.product_serial, self.supplier_location)
  155. self.assertEqual(sum(supplier_quants.mapped('quantity')), -5.0)
  156. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 5.0)
  157. self.assertEqual(len(self.gather_relevant(self.product_serial, self.supplier_location)), 5.0)
  158. quants = self.gather_relevant(self.product_serial, self.stock_location)
  159. self.assertEqual(len(quants), 5.0)
  160. for quant in quants:
  161. self.assertNotEqual(quant.in_date, False)
  162. def test_out_1(self):
  163. """ Send products to a client. Check that a move line is created reserving products in
  164. stock and that the delivery correctly remove the single quant in stock.
  165. """
  166. # make some stock
  167. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 100)
  168. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  169. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 100.0)
  170. # creation
  171. move1 = self.env['stock.move'].create({
  172. 'name': 'test_out_1',
  173. 'location_id': self.stock_location.id,
  174. 'location_dest_id': self.customer_location.id,
  175. 'product_id': self.product.id,
  176. 'product_uom': self.uom_unit.id,
  177. 'product_uom_qty': 100.0,
  178. })
  179. self.assertEqual(move1.state, 'draft')
  180. # confirmation
  181. move1._action_confirm()
  182. self.assertEqual(move1.state, 'confirmed')
  183. # assignment
  184. move1._action_assign()
  185. self.assertEqual(move1.state, 'assigned')
  186. self.assertEqual(len(move1.move_line_ids), 1)
  187. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  188. # Should be a reserved quantity and thus a quant.
  189. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  190. # fill the move line
  191. move_line = move1.move_line_ids[0]
  192. self.assertEqual(move_line.reserved_qty, 100.0)
  193. self.assertEqual(move_line.qty_done, 0.0)
  194. move_line.qty_done = 100.0
  195. with self.assertRaises(UserError, msg="It should not be possible to write directly to reserved_qty"):
  196. move_line.reserved_qty = 1.0
  197. # validation
  198. move1._action_done()
  199. self.assertEqual(move1.state, 'done')
  200. # Check there is one quant in customer location
  201. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 100.0)
  202. self.assertEqual(len(self.gather_relevant(self.product, self.customer_location)), 1.0)
  203. # there should be no quant amymore in the stock location
  204. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  205. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  206. def test_out_2(self):
  207. """ Send a consumable product to a client. Check that a move line is created but
  208. quants are not impacted.
  209. """
  210. # make some stock
  211. self.product.type = 'consu'
  212. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  213. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  214. # creation
  215. move1 = self.env['stock.move'].create({
  216. 'name': 'test_out_1',
  217. 'location_id': self.stock_location.id,
  218. 'location_dest_id': self.customer_location.id,
  219. 'product_id': self.product.id,
  220. 'product_uom': self.uom_unit.id,
  221. 'product_uom_qty': 100.0,
  222. })
  223. self.assertEqual(move1.state, 'draft')
  224. # confirmation
  225. move1._action_confirm()
  226. self.assertEqual(move1.state, 'assigned')
  227. self.assertEqual(len(move1.move_line_ids), 1)
  228. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  229. # Should be a reserved quantity and thus a quant.
  230. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  231. # fill the move line
  232. move_line = move1.move_line_ids[0]
  233. self.assertEqual(move_line.reserved_qty, 100.0)
  234. self.assertEqual(move_line.qty_done, 0.0)
  235. move_line.qty_done = 100.0
  236. # validation
  237. move1._action_done()
  238. self.assertEqual(move1.state, 'done')
  239. # no quants are created in the customer location since it's a consumable
  240. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 0.0)
  241. self.assertEqual(len(self.gather_relevant(self.product, self.customer_location)), 0.0)
  242. # there should be no quant amymore in the stock location
  243. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  244. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  245. def test_mixed_tracking_reservation_1(self):
  246. """ Send products tracked by lot to a customer. In your stock, there are tracked and
  247. untracked quants. Two moves lines should be created: one for the tracked ones, another
  248. for the untracked ones.
  249. """
  250. lot1 = self.env['stock.lot'].create({
  251. 'name': 'lot1',
  252. 'product_id': self.product_lot.id,
  253. 'company_id': self.env.company.id,
  254. })
  255. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 2)
  256. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 3, lot_id=lot1)
  257. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location), 5.0)
  258. # creation
  259. move1 = self.env['stock.move'].create({
  260. 'name': 'test_in_1',
  261. 'location_id': self.stock_location.id,
  262. 'location_dest_id': self.customer_location.id,
  263. 'product_id': self.product_lot.id,
  264. 'product_uom': self.uom_unit.id,
  265. 'product_uom_qty': 5.0,
  266. })
  267. move1._action_confirm()
  268. move1._action_assign()
  269. self.assertEqual(len(move1.move_line_ids), 2)
  270. def test_mixed_tracking_reservation_2(self):
  271. """ Send products tracked by lot to a customer. In your stock, there are two tracked and
  272. mulitple untracked quants. There should be as many move lines as there are quants
  273. reserved. Edit the reserve move lines to set them to new serial numbers, the reservation
  274. should stay. Validate and the final quantity in stock should be 0, not negative.
  275. """
  276. lot1 = self.env['stock.lot'].create({
  277. 'name': 'lot1',
  278. 'product_id': self.product_serial.id,
  279. 'company_id': self.env.company.id,
  280. })
  281. lot2 = self.env['stock.lot'].create({
  282. 'name': 'lot2',
  283. 'product_id': self.product_serial.id,
  284. 'company_id': self.env.company.id,
  285. })
  286. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 2)
  287. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot1)
  288. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot2)
  289. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 4.0)
  290. # creation
  291. move1 = self.env['stock.move'].create({
  292. 'name': 'test_in_1',
  293. 'location_id': self.stock_location.id,
  294. 'location_dest_id': self.customer_location.id,
  295. 'product_id': self.product_serial.id,
  296. 'product_uom': self.uom_unit.id,
  297. 'product_uom_qty': 4.0,
  298. })
  299. move1._action_confirm()
  300. move1._action_assign()
  301. self.assertEqual(len(move1.move_line_ids), 4)
  302. for ml in move1.move_line_ids:
  303. self.assertEqual(ml.reserved_qty, 1.0)
  304. # assign lot3 and lot 4 to both untracked move lines
  305. lot3 = self.env['stock.lot'].create({
  306. 'name': 'lot3',
  307. 'product_id': self.product_serial.id,
  308. 'company_id': self.env.company.id,
  309. })
  310. lot4 = self.env['stock.lot'].create({
  311. 'name': 'lot4',
  312. 'product_id': self.product_serial.id,
  313. 'company_id': self.env.company.id,
  314. })
  315. untracked_move_line = move1.move_line_ids.filtered(lambda ml: not ml.lot_id)
  316. untracked_move_line[0].lot_id = lot3
  317. untracked_move_line[1].lot_id = lot4
  318. for ml in move1.move_line_ids:
  319. self.assertEqual(ml.reserved_qty, 1.0)
  320. # no changes on quants, even if i made some move lines with a lot id whom reserved on untracked quants
  321. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, strict=True)), 1.0) # with a qty of 2
  322. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot1, strict=True).filtered(lambda q: q.lot_id)), 1.0)
  323. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot2, strict=True).filtered(lambda q: q.lot_id)), 1.0)
  324. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot3, strict=True).filtered(lambda q: q.lot_id)), 0)
  325. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location, lot_id=lot4, strict=True).filtered(lambda q: q.lot_id)), 0)
  326. move1.move_line_ids.write({'qty_done': 1.0})
  327. move1._action_done()
  328. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 0.0)
  329. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 0.0)
  330. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot2, strict=True), 0.0)
  331. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot3, strict=True), 0.0)
  332. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot4, strict=True), 0.0)
  333. def test_mixed_tracking_reservation_3(self):
  334. """ Send two products tracked by lot to a customer. In your stock, there two tracked quants
  335. and two untracked. Once the move is validated, add move lines to also move the two untracked
  336. ones and assign them serial numbers on the fly. The final quantity in stock should be 0, not
  337. negative.
  338. """
  339. lot1 = self.env['stock.lot'].create({
  340. 'name': 'lot1',
  341. 'product_id': self.product_serial.id,
  342. 'company_id': self.env.company.id,
  343. })
  344. lot2 = self.env['stock.lot'].create({
  345. 'name': 'lot2',
  346. 'product_id': self.product_serial.id,
  347. 'company_id': self.env.company.id,
  348. })
  349. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot1)
  350. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot2)
  351. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 2.0)
  352. # creation
  353. move1 = self.env['stock.move'].create({
  354. 'name': 'test_in_1',
  355. 'location_id': self.stock_location.id,
  356. 'location_dest_id': self.customer_location.id,
  357. 'product_id': self.product_serial.id,
  358. 'product_uom': self.uom_unit.id,
  359. 'product_uom_qty': 2.0,
  360. })
  361. move1._action_confirm()
  362. move1._action_assign()
  363. move1.move_line_ids.write({'qty_done': 1.0})
  364. move1._action_done()
  365. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 2)
  366. lot3 = self.env['stock.lot'].create({
  367. 'name': 'lot3',
  368. 'product_id': self.product_serial.id,
  369. 'company_id': self.env.company.id,
  370. })
  371. lot4 = self.env['stock.lot'].create({
  372. 'name': 'lot4',
  373. 'product_id': self.product_serial.id,
  374. 'company_id': self.env.company.id,
  375. })
  376. self.env['stock.move.line'].create({
  377. 'move_id': move1.id,
  378. 'product_id': move1.product_id.id,
  379. 'qty_done': 1,
  380. 'product_uom_id': move1.product_uom.id,
  381. 'location_id': move1.location_id.id,
  382. 'location_dest_id': move1.location_dest_id.id,
  383. 'lot_id': lot3.id,
  384. })
  385. self.env['stock.move.line'].create({
  386. 'move_id': move1.id,
  387. 'product_id': move1.product_id.id,
  388. 'qty_done': 1,
  389. 'product_uom_id': move1.product_uom.id,
  390. 'location_id': move1.location_id.id,
  391. 'location_dest_id': move1.location_dest_id.id,
  392. 'lot_id': lot4.id
  393. })
  394. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 0.0)
  395. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 0.0)
  396. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot2, strict=True), 0.0)
  397. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot3, strict=True), 0.0)
  398. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot4, strict=True), 0.0)
  399. def test_mixed_tracking_reservation_4(self):
  400. """ Send two products tracked by lot to a customer. In your stock, there two tracked quants
  401. and on untracked. Once the move is validated, edit one of the done move line to change the
  402. serial number to one that is not in stock. The original serial should go back to stock and
  403. the untracked quant should be tracked on the fly and sent instead.
  404. """
  405. lot1 = self.env['stock.lot'].create({
  406. 'name': 'lot1',
  407. 'product_id': self.product_serial.id,
  408. 'company_id': self.env.company.id,
  409. })
  410. lot2 = self.env['stock.lot'].create({
  411. 'name': 'lot2',
  412. 'product_id': self.product_serial.id,
  413. 'company_id': self.env.company.id,
  414. })
  415. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot1)
  416. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot2)
  417. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 2.0)
  418. # creation
  419. move1 = self.env['stock.move'].create({
  420. 'name': 'test_in_1',
  421. 'location_id': self.stock_location.id,
  422. 'location_dest_id': self.customer_location.id,
  423. 'product_id': self.product_serial.id,
  424. 'product_uom': self.uom_unit.id,
  425. 'product_uom_qty': 2.0,
  426. })
  427. move1._action_confirm()
  428. move1._action_assign()
  429. move1.move_line_ids.write({'qty_done': 1.0})
  430. move1._action_done()
  431. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 0.0)
  432. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 0.0)
  433. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot2, strict=True), 0.0)
  434. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1)
  435. lot3 = self.env['stock.lot'].create({
  436. 'name': 'lot3',
  437. 'product_id': self.product_serial.id,
  438. 'company_id': self.env.company.id,
  439. })
  440. move1.move_line_ids[1].lot_id = lot3
  441. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 1.0)
  442. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 0.0)
  443. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot2, strict=True), 1.0)
  444. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot3, strict=True), 0.0)
  445. def test_mixed_tracking_reservation_5(self):
  446. move1 = self.env['stock.move'].create({
  447. 'name': 'test_jenaimarre_1',
  448. 'location_id': self.stock_location.id,
  449. 'location_dest_id': self.customer_location.id,
  450. 'product_id': self.product_serial.id,
  451. 'product_uom': self.uom_unit.id,
  452. 'product_uom_qty': 1.0,
  453. })
  454. move1._action_confirm()
  455. move1._action_assign()
  456. self.assertEqual(move1.state, 'confirmed')
  457. # create an untracked quant
  458. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1.0)
  459. lot1 = self.env['stock.lot'].create({
  460. 'name': 'lot1',
  461. 'product_id': self.product_serial.id,
  462. 'company_id': self.env.company.id,
  463. })
  464. # create a new move line with a lot not assigned to any quant
  465. self.env['stock.move.line'].create({
  466. 'move_id': move1.id,
  467. 'product_id': move1.product_id.id,
  468. 'qty_done': 1,
  469. 'product_uom_id': move1.product_uom.id,
  470. 'location_id': move1.location_id.id,
  471. 'location_dest_id': move1.location_dest_id.id,
  472. 'lot_id': lot1.id
  473. })
  474. self.assertEqual(len(move1.move_line_ids), 1)
  475. self.assertEqual(move1.reserved_availability, 0)
  476. # validating the move line should move the lot, not create a negative quant in stock
  477. move1._action_done()
  478. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 0.0)
  479. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location)), 0.0)
  480. def test_mixed_tracking_reservation_6(self):
  481. # create an untracked quant
  482. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1.0)
  483. move1 = self.env['stock.move'].create({
  484. 'name': 'test_jenaimarre_1',
  485. 'location_id': self.stock_location.id,
  486. 'location_dest_id': self.customer_location.id,
  487. 'product_id': self.product_serial.id,
  488. 'product_uom': self.uom_unit.id,
  489. 'product_uom_qty': 1.0,
  490. })
  491. move1._action_confirm()
  492. move1._action_assign()
  493. self.assertEqual(move1.state, 'assigned')
  494. lot1 = self.env['stock.lot'].create({
  495. 'name': 'lot1',
  496. 'product_id': self.product_serial.id,
  497. 'company_id': self.env.company.id,
  498. })
  499. lot2 = self.env['stock.lot'].create({
  500. 'name': 'lot2',
  501. 'product_id': self.product_serial.id,
  502. 'company_id': self.env.company.id,
  503. })
  504. move_line = move1.move_line_ids
  505. move_line.lot_id = lot1
  506. self.assertEqual(move_line.reserved_qty, 1.0)
  507. move_line.lot_id = lot2
  508. self.assertEqual(move_line.reserved_qty, 1.0)
  509. move_line.qty_done = 1
  510. # validating the move line should move the lot, not create a negative quant in stock
  511. move1._action_done()
  512. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 0.0)
  513. self.assertEqual(len(self.gather_relevant(self.product_serial, self.stock_location)), 0.0)
  514. def test_mixed_tracking_reservation_7(self):
  515. """ Similar test_mixed_tracking_reservation_2 but creates first the tracked quant, then the
  516. untracked ones. When adding a lot to the untracked move line, it should not decrease the
  517. untracked quant then increase a non-existing tracked one that will fallback on the
  518. untracked quant.
  519. """
  520. lot1 = self.env['stock.lot'].create({
  521. 'name': 'lot1',
  522. 'product_id': self.product_serial.id,
  523. 'company_id': self.env.company.id,
  524. })
  525. lot2 = self.env['stock.lot'].create({
  526. 'name': 'lot2',
  527. 'product_id': self.product_serial.id,
  528. 'company_id': self.env.company.id,
  529. })
  530. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot1)
  531. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1)
  532. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 2.0)
  533. # creation
  534. move1 = self.env['stock.move'].create({
  535. 'name': 'test_in_1',
  536. 'location_id': self.stock_location.id,
  537. 'location_dest_id': self.customer_location.id,
  538. 'product_id': self.product_serial.id,
  539. 'product_uom': self.uom_unit.id,
  540. 'product_uom_qty': 2.0,
  541. })
  542. move1._action_confirm()
  543. move1._action_assign()
  544. self.assertEqual(len(move1.move_line_ids), 2)
  545. for ml in move1.move_line_ids:
  546. self.assertEqual(ml.reserved_qty, 1.0)
  547. untracked_move_line = move1.move_line_ids.filtered(lambda ml: not ml.lot_id).lot_id = lot2
  548. for ml in move1.move_line_ids:
  549. self.assertEqual(ml.reserved_qty, 1.0)
  550. move1.move_line_ids.write({'qty_done': 1.0})
  551. move1._action_done()
  552. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location), 0.0)
  553. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 0.0)
  554. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot2, strict=True), 0.0)
  555. quants = self.gather_relevant(self.product_serial, self.stock_location)
  556. self.assertEqual(len(quants), 0)
  557. def test_mixed_tracking_reservation_8(self):
  558. """ Send one product tracked by lot to a customer. In your stock, there are one tracked and
  559. one untracked quant. Reserve the move, then edit the lot to one not present in stock. The
  560. system will update the reservation and use the untracked quant. Now unreserve, no error
  561. should happen
  562. """
  563. lot1 = self.env['stock.lot'].create({
  564. 'name': 'lot1',
  565. 'product_id': self.product_serial.id,
  566. 'company_id': self.env.company.id,
  567. })
  568. # at first, we only make the tracked quant available in stock to make sure this one is selected
  569. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1, lot_id=lot1)
  570. # creation
  571. move1 = self.env['stock.move'].create({
  572. 'name': 'test_mixed_tracking_reservation_7',
  573. 'location_id': self.stock_location.id,
  574. 'location_dest_id': self.customer_location.id,
  575. 'product_id': self.product_serial.id,
  576. 'product_uom': self.uom_unit.id,
  577. 'product_uom_qty': 1.0,
  578. })
  579. move1._action_confirm()
  580. move1._action_assign()
  581. self.assertEqual(move1.reserved_availability, 1.0)
  582. self.assertEqual(move1.move_line_ids.lot_id.id, lot1.id)
  583. # change the lot_id to one not available in stock while an untracked quant is available
  584. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1)
  585. lot2 = self.env['stock.lot'].create({
  586. 'name': 'lot2',
  587. 'product_id': self.product_serial.id,
  588. 'company_id': self.env.company.id,
  589. })
  590. move1.move_line_ids.lot_id = lot2
  591. self.assertEqual(move1.reserved_availability, 1.0)
  592. self.assertEqual(move1.move_line_ids.lot_id.id, lot2.id)
  593. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 0.0)
  594. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 1.0)
  595. # unreserve
  596. move1._do_unreserve()
  597. self.assertEqual(move1.reserved_availability, 0.0)
  598. self.assertEqual(len(move1.move_line_ids), 0)
  599. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, strict=True), 1.0)
  600. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.stock_location, lot_id=lot1, strict=True), 2.0)
  601. def test_putaway_1(self):
  602. """ Receive products from a supplier. Check that putaway rules are rightly applied on
  603. the receipt move line.
  604. """
  605. # This test will apply a putaway strategy on the stock location to put everything
  606. # incoming in the sublocation shelf1.
  607. shelf1_location = self.env['stock.location'].create({
  608. 'name': 'shelf1',
  609. 'usage': 'internal',
  610. 'location_id': self.stock_location.id,
  611. })
  612. # putaway from stock to shelf1
  613. putaway = self.env['stock.putaway.rule'].create({
  614. 'category_id': self.env.ref('product.product_category_all').id,
  615. 'location_in_id': self.stock_location.id,
  616. 'location_out_id': shelf1_location.id,
  617. })
  618. self.stock_location.write({
  619. 'putaway_rule_ids': [(4, putaway.id, 0)]
  620. })
  621. # creation
  622. move1 = self.env['stock.move'].create({
  623. 'name': 'test_putaway_1',
  624. 'location_id': self.supplier_location.id,
  625. 'location_dest_id': self.stock_location.id,
  626. 'product_id': self.product.id,
  627. 'product_uom': self.uom_unit.id,
  628. 'product_uom_qty': 100.0,
  629. })
  630. move1._action_confirm()
  631. self.assertEqual(move1.state, 'assigned')
  632. self.assertEqual(len(move1.move_line_ids), 1)
  633. # check if the putaway was rightly applied
  634. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf1_location.id)
  635. def test_putaway_2(self):
  636. """ Receive products from a supplier. Check that putaway rules are rightly applied on
  637. the receipt move line.
  638. """
  639. # This test will apply a putaway strategy by product on the stock location to put everything
  640. # incoming in the sublocation shelf1.
  641. shelf1_location = self.env['stock.location'].create({
  642. 'name': 'shelf1',
  643. 'usage': 'internal',
  644. 'location_id': self.stock_location.id,
  645. })
  646. # putaway from stock to shelf1
  647. putaway = self.env['stock.putaway.rule'].create({
  648. 'product_id': self.product.id,
  649. 'location_in_id': self.stock_location.id,
  650. 'location_out_id': shelf1_location.id,
  651. })
  652. self.stock_location.write({
  653. 'putaway_rule_ids': [(4, putaway.id, 0)],
  654. })
  655. # creation
  656. move1 = self.env['stock.move'].create({
  657. 'name': 'test_putaway_2',
  658. 'location_id': self.supplier_location.id,
  659. 'location_dest_id': self.stock_location.id,
  660. 'product_id': self.product.id,
  661. 'product_uom': self.uom_unit.id,
  662. 'product_uom_qty': 100.0,
  663. })
  664. move1._action_confirm()
  665. self.assertEqual(move1.state, 'assigned')
  666. self.assertEqual(len(move1.move_line_ids), 1)
  667. # check if the putaway was rightly applied
  668. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf1_location.id)
  669. def test_putaway_3(self):
  670. """ Receive products from a supplier. Check that putaway rules are rightly applied on
  671. the receipt move line.
  672. """
  673. # This test will apply both the putaway strategy by product and category. We check here
  674. # that the putaway by product takes precedence.
  675. shelf1_location = self.env['stock.location'].create({
  676. 'name': 'shelf1',
  677. 'usage': 'internal',
  678. 'location_id': self.stock_location.id,
  679. })
  680. shelf2_location = self.env['stock.location'].create({
  681. 'name': 'shelf2',
  682. 'usage': 'internal',
  683. 'location_id': self.stock_location.id,
  684. })
  685. putaway_category = self.env['stock.putaway.rule'].create({
  686. 'category_id': self.env.ref('product.product_category_all').id,
  687. 'location_in_id': self.supplier_location.id,
  688. 'location_out_id': shelf1_location.id,
  689. })
  690. putaway_product = self.env['stock.putaway.rule'].create({
  691. 'product_id': self.product.id,
  692. 'location_in_id': self.supplier_location.id,
  693. 'location_out_id': shelf2_location.id,
  694. })
  695. self.stock_location.write({
  696. 'putaway_rule_ids': [(6, 0, [
  697. putaway_category.id,
  698. putaway_product.id
  699. ])],
  700. })
  701. # creation
  702. move1 = self.env['stock.move'].create({
  703. 'name': 'test_putaway_3',
  704. 'location_id': self.supplier_location.id,
  705. 'location_dest_id': self.stock_location.id,
  706. 'product_id': self.product.id,
  707. 'product_uom': self.uom_unit.id,
  708. 'product_uom_qty': 100.0,
  709. })
  710. move1._action_confirm()
  711. self.assertEqual(move1.state, 'assigned')
  712. self.assertEqual(len(move1.move_line_ids), 1)
  713. # check if the putaway was rightly applied
  714. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf2_location.id)
  715. def test_putaway_4(self):
  716. """ Receive products from a supplier. Check that putaway rules are rightly applied on
  717. the receipt move line.
  718. """
  719. # This test will apply both the putaway strategy by product and category. We check here
  720. # that if a putaway by product is not matched, the fallback to the category is correctly
  721. # done.
  722. shelf1_location = self.env['stock.location'].create({
  723. 'name': 'shelf1',
  724. 'usage': 'internal',
  725. 'location_id': self.stock_location.id,
  726. })
  727. shelf2_location = self.env['stock.location'].create({
  728. 'name': 'shelf2',
  729. 'usage': 'internal',
  730. 'location_id': self.stock_location.id,
  731. })
  732. # putaway from stock to shelf1
  733. putaway_category = self.env['stock.putaway.rule'].create({
  734. 'category_id': self.env.ref('product.product_category_all').id,
  735. 'location_in_id': self.stock_location.id,
  736. 'location_out_id': shelf1_location.id,
  737. })
  738. putaway_product = self.env['stock.putaway.rule'].create({
  739. 'product_id': self.product_consu.id,
  740. 'location_in_id': self.stock_location.id,
  741. 'location_out_id': shelf2_location.id,
  742. })
  743. self.stock_location.write({
  744. 'putaway_rule_ids': [(6, 0, [
  745. putaway_category.id,
  746. putaway_product.id,
  747. ])],
  748. })
  749. # creation
  750. move1 = self.env['stock.move'].create({
  751. 'name': 'test_putaway_4',
  752. 'location_id': self.supplier_location.id,
  753. 'location_dest_id': self.stock_location.id,
  754. 'product_id': self.product.id,
  755. 'product_uom': self.uom_unit.id,
  756. 'product_uom_qty': 100.0,
  757. })
  758. move1._action_confirm()
  759. self.assertEqual(move1.state, 'assigned')
  760. self.assertEqual(len(move1.move_line_ids), 1)
  761. # check if the putaway was rightly applied
  762. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf1_location.id)
  763. def test_putaway_5(self):
  764. """ Receive products from a supplier. Check that putaway rules are rightly applied on
  765. the receipt move line.
  766. """
  767. # This test will apply putaway strategy by category.
  768. # We check here that the putaway by category works when the category is
  769. # set on parent category of the product.
  770. shelf_location = self.env['stock.location'].create({
  771. 'name': 'shelf',
  772. 'usage': 'internal',
  773. 'location_id': self.stock_location.id,
  774. })
  775. putaway = self.env['stock.putaway.rule'].create({
  776. 'category_id': self.env.ref('product.product_category_all').id,
  777. 'location_in_id': self.supplier_location.id,
  778. 'location_out_id': shelf_location.id,
  779. })
  780. self.stock_location.write({
  781. 'putaway_rule_ids': [(6, 0, [
  782. putaway.id,
  783. ])],
  784. })
  785. # creation
  786. move1 = self.env['stock.move'].create({
  787. 'name': 'test_putaway_5',
  788. 'location_id': self.supplier_location.id,
  789. 'location_dest_id': self.stock_location.id,
  790. 'product_id': self.product.id,
  791. 'product_uom': self.uom_unit.id,
  792. 'product_uom_qty': 100.0,
  793. })
  794. move1._action_confirm()
  795. self.assertEqual(move1.state, 'assigned')
  796. self.assertEqual(len(move1.move_line_ids), 1)
  797. # check if the putaway was rightly applied
  798. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf_location.id)
  799. def test_putaway_6(self):
  800. """ Receive products from a supplier. Check that putaway rules are rightly applied on
  801. the receipt move line.
  802. """
  803. # This test will apply two putaway strategies by category. We check here
  804. # that the most specific putaway takes precedence.
  805. child_category = self.env['product.category'].create({
  806. 'name': 'child_category',
  807. 'parent_id': self.ref('product.product_category_all'),
  808. })
  809. shelf1_location = self.env['stock.location'].create({
  810. 'name': 'shelf1',
  811. 'usage': 'internal',
  812. 'location_id': self.stock_location.id,
  813. })
  814. shelf2_location = self.env['stock.location'].create({
  815. 'name': 'shelf2',
  816. 'usage': 'internal',
  817. 'location_id': self.stock_location.id,
  818. })
  819. putaway_category_all = self.env['stock.putaway.rule'].create({
  820. 'category_id': self.env.ref('product.product_category_all').id,
  821. 'location_in_id': self.supplier_location.id,
  822. 'location_out_id': shelf1_location.id,
  823. })
  824. putaway_category_office_furn = self.env['stock.putaway.rule'].create({
  825. 'category_id': child_category.id,
  826. 'location_in_id': self.supplier_location.id,
  827. 'location_out_id': shelf2_location.id,
  828. })
  829. self.stock_location.write({
  830. 'putaway_rule_ids': [(6, 0, [
  831. putaway_category_all.id,
  832. putaway_category_office_furn.id,
  833. ])],
  834. })
  835. self.product.categ_id = child_category
  836. # creation
  837. move1 = self.env['stock.move'].create({
  838. 'name': 'test_putaway_6',
  839. 'location_id': self.supplier_location.id,
  840. 'location_dest_id': self.stock_location.id,
  841. 'product_id': self.product.id,
  842. 'product_uom': self.uom_unit.id,
  843. 'product_uom_qty': 100.0,
  844. })
  845. move1._action_confirm()
  846. self.assertEqual(move1.state, 'assigned')
  847. self.assertEqual(len(move1.move_line_ids), 1)
  848. # check if the putaway was rightly applied
  849. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf2_location.id)
  850. def test_putaway_7(self):
  851. """
  852. Putaway with one package type and one product
  853. """
  854. warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
  855. warehouse.reception_steps = 'two_steps'
  856. child_loc = self.stock_location.child_ids[0]
  857. package_type = self.env['stock.package.type'].create({
  858. 'name': 'Super Package Type',
  859. })
  860. package = self.env['stock.quant.package'].create({'package_type_id': package_type.id})
  861. self.env['stock.putaway.rule'].create({
  862. 'product_id': self.product.id,
  863. 'package_type_ids': [(6, 0, package_type.ids)],
  864. 'location_in_id': self.stock_location.id,
  865. 'location_out_id': child_loc.id,
  866. })
  867. move_input = self.env['stock.move'].create({
  868. 'name': self.product.name,
  869. 'location_id': self.supplier_location.id,
  870. 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
  871. 'product_id': self.product.id,
  872. 'product_uom': self.uom_unit.id,
  873. 'product_uom_qty': 1.0,
  874. 'warehouse_id': warehouse.id,
  875. })
  876. move_input._action_confirm()
  877. move_input.move_line_ids.qty_done = 1
  878. move_input.move_line_ids.result_package_id = package
  879. move_input._action_done()
  880. move_stock = move_input.move_dest_ids
  881. self.assertEqual(move_stock.move_line_ids.location_dest_id, child_loc)
  882. def test_putaway_8(self):
  883. """
  884. Putaway with product P
  885. Receive 1 x P in a package with a specific type
  886. """
  887. warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
  888. warehouse.reception_steps = 'two_steps'
  889. child_loc = self.stock_location.child_ids[0]
  890. package_type = self.env['stock.package.type'].create({
  891. 'name': 'Super Package Type',
  892. })
  893. package = self.env['stock.quant.package'].create({'package_type_id': package_type.id})
  894. self.env['stock.putaway.rule'].create({
  895. 'product_id': self.product.id,
  896. 'location_in_id': self.stock_location.id,
  897. 'location_out_id': child_loc.id,
  898. })
  899. move_input = self.env['stock.move'].create({
  900. 'name': self.product.name,
  901. 'location_id': self.supplier_location.id,
  902. 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
  903. 'product_id': self.product.id,
  904. 'product_uom': self.uom_unit.id,
  905. 'product_uom_qty': 1.0,
  906. 'warehouse_id': warehouse.id,
  907. })
  908. move_input._action_confirm()
  909. move_input.move_line_ids.qty_done = 1
  910. move_input.move_line_ids.result_package_id = package
  911. move_input._action_done()
  912. move_stock = move_input.move_dest_ids
  913. self.assertEqual(move_stock.move_line_ids.location_dest_id, child_loc)
  914. def test_putaway_9(self):
  915. """
  916. Putaway with one category C
  917. 2 steps receive
  918. Receive one C-type product in a package with a specific type
  919. The putaway should be selected
  920. """
  921. warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
  922. warehouse.reception_steps = 'two_steps'
  923. basic_category = self.env.ref('product.product_category_all')
  924. child_locations = self.env['stock.location']
  925. categs = self.env['product.category']
  926. for i in range(3):
  927. loc = self.env['stock.location'].create({
  928. 'name': 'shelf %s' % i,
  929. 'usage': 'internal',
  930. 'location_id': self.stock_location.id,
  931. })
  932. child_locations |= loc
  933. categ = self.env['product.category'].create({
  934. 'name': 'Category %s' % i,
  935. 'parent_id': basic_category.id
  936. })
  937. categs |= categ
  938. self.env['stock.putaway.rule'].create({
  939. 'category_id': categ.id,
  940. 'location_in_id': self.stock_location.id,
  941. 'location_out_id': loc.id,
  942. })
  943. second_child_location = child_locations[1]
  944. second_categ = categs[1]
  945. self.product.categ_id = second_categ
  946. package_type = self.env['stock.package.type'].create({
  947. 'name': 'Super Package Type',
  948. })
  949. package = self.env['stock.quant.package'].create({
  950. 'package_type_id': package_type.id,
  951. })
  952. move_input = self.env['stock.move'].create({
  953. 'name': self.product.name,
  954. 'location_id': self.supplier_location.id,
  955. 'location_dest_id': warehouse.wh_input_stock_loc_id.id,
  956. 'product_id': self.product.id,
  957. 'product_uom': self.uom_unit.id,
  958. 'product_uom_qty': 1.0,
  959. 'warehouse_id': warehouse.id,
  960. })
  961. move_input._action_confirm()
  962. move_input.move_line_ids.qty_done = 1
  963. move_input.move_line_ids.result_package_id = package
  964. move_input._action_done()
  965. move_stock = move_input.move_dest_ids
  966. self.assertEqual(move_stock.move_line_ids.location_dest_id, second_child_location)
  967. def test_putaway_with_storage_category_1(self):
  968. """Receive a product. Test the product will be move to a child location
  969. with correct storage category.
  970. """
  971. # storage category
  972. storage_category = self.env['stock.storage.category'].create({
  973. 'name': "storage category"
  974. })
  975. shelf1_location = self.env['stock.location'].create({
  976. 'name': 'shelf1',
  977. 'usage': 'internal',
  978. 'location_id': self.stock_location.id,
  979. })
  980. shelf2_location = self.env['stock.location'].create({
  981. 'name': 'shelf2',
  982. 'usage': 'internal',
  983. 'location_id': self.stock_location.id,
  984. 'storage_category_id': storage_category.id,
  985. })
  986. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  987. # putaway from stock to child location with storage_category
  988. putaway = self.env['stock.putaway.rule'].create({
  989. 'product_id': self.product.id,
  990. 'location_in_id': self.stock_location.id,
  991. 'location_out_id': self.stock_location.id,
  992. 'storage_category_id': storage_category.id,
  993. })
  994. self.stock_location.write({
  995. 'putaway_rule_ids': [(4, putaway.id, 0)],
  996. })
  997. move1 = self.env['stock.move'].create({
  998. 'name': 'test_move_1',
  999. 'location_id': self.supplier_location.id,
  1000. 'location_dest_id': self.stock_location.id,
  1001. 'product_id': self.product.id,
  1002. 'product_uom': self.uom_unit.id,
  1003. 'product_uom_qty': 100.0,
  1004. })
  1005. move1._action_confirm()
  1006. self.assertEqual(move1.state, 'assigned')
  1007. self.assertEqual(len(move1.move_line_ids), 1)
  1008. # check if the putaway was rightly applied
  1009. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf2_location.id)
  1010. def test_putaway_with_storage_category_2(self):
  1011. """Receive a product twice. Test first time the putaway applied since we
  1012. have enough space, and second time it is not since the location is full.
  1013. """
  1014. storage_category = self.env['stock.storage.category'].create({
  1015. 'name': "storage category"
  1016. })
  1017. # set the capacity for the product in this storage category to be 100
  1018. storage_category_form = Form(storage_category, view='stock.stock_storage_category_form')
  1019. with storage_category_form.product_capacity_ids.new() as line:
  1020. line.product_id = self.product
  1021. line.quantity = 100
  1022. storage_category = storage_category_form.save()
  1023. shelf1_location = self.env['stock.location'].create({
  1024. 'name': 'shelf1',
  1025. 'usage': 'internal',
  1026. 'location_id': self.stock_location.id,
  1027. 'storage_category_id': storage_category.id,
  1028. })
  1029. # putaway from stock to child location with storage_category
  1030. putaway = self.env['stock.putaway.rule'].create({
  1031. 'product_id': self.product.id,
  1032. 'location_in_id': self.stock_location.id,
  1033. 'location_out_id': self.stock_location.id,
  1034. 'storage_category_id': storage_category.id,
  1035. })
  1036. self.stock_location.write({
  1037. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1038. })
  1039. # first move
  1040. move1 = self.env['stock.move'].create({
  1041. 'name': 'test_move_1',
  1042. 'location_id': self.supplier_location.id,
  1043. 'location_dest_id': self.stock_location.id,
  1044. 'product_id': self.product.id,
  1045. 'product_uom': self.uom_unit.id,
  1046. 'product_uom_qty': 100.0,
  1047. })
  1048. move1._action_confirm()
  1049. self.assertEqual(move1.state, 'assigned')
  1050. self.assertEqual(len(move1.move_line_ids), 1)
  1051. # check if the putaway was rightly applied
  1052. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf1_location.id)
  1053. # second move
  1054. move2 = self.env['stock.move'].create({
  1055. 'name': 'test_move_2',
  1056. 'location_id': self.supplier_location.id,
  1057. 'location_dest_id': self.stock_location.id,
  1058. 'product_id': self.product.id,
  1059. 'product_uom': self.uom_unit.id,
  1060. 'product_uom_qty': 100.0,
  1061. })
  1062. move2._action_confirm()
  1063. self.assertEqual(move1.state, 'assigned')
  1064. self.assertEqual(len(move2.move_line_ids), 1)
  1065. # check if the putaway wasn't applied
  1066. self.assertEqual(move2.move_line_ids.location_dest_id.id, self.stock_location.id)
  1067. def test_putaway_with_storage_category_3(self):
  1068. """Received products twice, set storage category to only accept new
  1069. product when empty. Check the first time putaway rule applied and second
  1070. time not.
  1071. """
  1072. storage_category = self.env['stock.storage.category'].create({
  1073. 'name': "storage category",
  1074. 'allow_new_product': "empty",
  1075. })
  1076. shelf1_location = self.env['stock.location'].create({
  1077. 'name': 'shelf1',
  1078. 'usage': 'internal',
  1079. 'location_id': self.stock_location.id,
  1080. 'storage_category_id': storage_category.id,
  1081. })
  1082. # putaway from stock to child location with storage_category
  1083. putaway = self.env['stock.putaway.rule'].create({
  1084. 'product_id': self.product.id,
  1085. 'location_in_id': self.stock_location.id,
  1086. 'location_out_id': self.stock_location.id,
  1087. 'storage_category_id': storage_category.id,
  1088. })
  1089. self.stock_location.write({
  1090. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1091. })
  1092. # first move
  1093. move1 = self.env['stock.move'].create({
  1094. 'name': 'test_move_1',
  1095. 'location_id': self.supplier_location.id,
  1096. 'location_dest_id': self.stock_location.id,
  1097. 'product_id': self.product.id,
  1098. 'product_uom': self.uom_unit.id,
  1099. 'product_uom_qty': 100.0,
  1100. })
  1101. move1._action_confirm()
  1102. self.assertEqual(move1.state, 'assigned')
  1103. self.assertEqual(len(move1.move_line_ids), 1)
  1104. move_line = move1.move_line_ids[0]
  1105. move_line.qty_done = 100
  1106. move1._action_done()
  1107. self.assertEqual(move1.state, 'done')
  1108. # check if the putaway was rightly applied
  1109. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf1_location.id)
  1110. # second move
  1111. move2 = self.env['stock.move'].create({
  1112. 'name': 'test_move_2',
  1113. 'location_id': self.supplier_location.id,
  1114. 'location_dest_id': self.stock_location.id,
  1115. 'product_id': self.product.id,
  1116. 'product_uom': self.uom_unit.id,
  1117. 'product_uom_qty': 100.0,
  1118. })
  1119. move2._action_confirm()
  1120. self.assertEqual(move2.state, 'assigned')
  1121. self.assertEqual(len(move2.move_line_ids), 1)
  1122. # check if the putaway wasn't applied
  1123. self.assertEqual(move2.move_line_ids.location_dest_id.id, self.stock_location.id)
  1124. def test_putaway_with_storage_category_4(self):
  1125. """Received products, set storage category to only accept same product.
  1126. Check the putaway rule can't be applied when the location has different
  1127. products.
  1128. """
  1129. storage_category = self.env['stock.storage.category'].create({
  1130. 'name': "storage category",
  1131. 'allow_new_product': "same",
  1132. })
  1133. shelf1_location = self.env['stock.location'].create({
  1134. 'name': 'shelf1',
  1135. 'usage': 'internal',
  1136. 'location_id': self.stock_location.id,
  1137. 'storage_category_id': storage_category.id,
  1138. })
  1139. # putaway from stock to child location with storage_category
  1140. putaway = self.env['stock.putaway.rule'].create({
  1141. 'product_id': self.product.id,
  1142. 'location_in_id': self.stock_location.id,
  1143. 'location_out_id': self.stock_location.id,
  1144. 'storage_category_id': storage_category.id,
  1145. })
  1146. self.stock_location.write({
  1147. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1148. })
  1149. # create a different product and its quant
  1150. product2 = self.env['product.product'].create({
  1151. 'name': 'Product 2',
  1152. 'type': 'product',
  1153. 'categ_id': self.env.ref('product.product_category_all').id,
  1154. })
  1155. self.env['stock.quant'].create({
  1156. 'product_id': product2.id,
  1157. 'product_uom_id': self.uom_unit.id,
  1158. 'location_id': shelf1_location.id,
  1159. 'quantity': 1,
  1160. 'reserved_quantity': 0,
  1161. })
  1162. move1 = self.env['stock.move'].create({
  1163. 'name': 'test_move_1',
  1164. 'location_id': self.supplier_location.id,
  1165. 'location_dest_id': self.stock_location.id,
  1166. 'product_id': self.product.id,
  1167. 'product_uom': self.uom_unit.id,
  1168. 'product_uom_qty': 100.0,
  1169. })
  1170. move1._action_confirm()
  1171. self.assertEqual(move1.state, 'assigned')
  1172. self.assertEqual(len(move1.move_line_ids), 1)
  1173. move_line = move1.move_line_ids[0]
  1174. move_line.qty_done = 100
  1175. move1._action_done()
  1176. self.assertEqual(move1.state, 'done')
  1177. # check if the putaway can't be applied
  1178. self.assertEqual(move1.move_line_ids.location_dest_id.id, self.stock_location.id)
  1179. def test_putaway_with_storage_category_5(self):
  1180. """Receive a package. Test the package will be move to a child location
  1181. with correct storage category.
  1182. """
  1183. # Required for `result_package_id` to be visible in the view
  1184. self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
  1185. # storage category
  1186. storage_category = self.env['stock.storage.category'].create({
  1187. 'name': "storage category"
  1188. })
  1189. package_type = self.env['stock.package.type'].create({
  1190. 'name': "package type",
  1191. })
  1192. self.env['stock.location'].create({
  1193. 'name': 'shelf1',
  1194. 'usage': 'internal',
  1195. 'location_id': self.stock_location.id,
  1196. })
  1197. shelf2_location = self.env['stock.location'].create({
  1198. 'name': 'shelf2',
  1199. 'usage': 'internal',
  1200. 'location_id': self.stock_location.id,
  1201. 'storage_category_id': storage_category.id,
  1202. })
  1203. # putaway from stock to child location with storage_category
  1204. putaway = self.env['stock.putaway.rule'].create({
  1205. 'product_id': self.product.id,
  1206. 'location_in_id': self.stock_location.id,
  1207. 'location_out_id': self.stock_location.id,
  1208. 'storage_category_id': storage_category.id,
  1209. 'package_type_ids': [(4, package_type.id, 0)],
  1210. })
  1211. self.stock_location.write({
  1212. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1213. })
  1214. package = self.env['stock.quant.package'].create({
  1215. 'name': 'package',
  1216. 'package_type_id': package_type.id,
  1217. })
  1218. move1 = self.env['stock.move'].create({
  1219. 'name': 'test_move_1',
  1220. 'location_id': self.supplier_location.id,
  1221. 'location_dest_id': self.stock_location.id,
  1222. 'product_id': self.product.id,
  1223. 'product_uom': self.uom_unit.id,
  1224. 'product_uom_qty': 100.0,
  1225. })
  1226. move1._action_confirm()
  1227. self.assertEqual(move1.state, 'assigned')
  1228. self.assertEqual(len(move1.move_line_ids), 1)
  1229. move_form = Form(move1, view='stock.view_stock_move_nosuggest_operations')
  1230. with move_form.move_line_nosuggest_ids.new() as line:
  1231. line.result_package_id = package
  1232. line.qty_done = 100
  1233. move1 = move_form.save()
  1234. move1._action_done()
  1235. # check if the putaway was rightly applied
  1236. self.assertEqual(package.location_id.id, shelf2_location.id)
  1237. def test_putaway_with_storage_category_6(self):
  1238. """Receive package with same package type twice. Check putaway rule can
  1239. be applied on the first one but not the second one due to no space.
  1240. """
  1241. # Required for `result_package_id` to be visible in the view
  1242. self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
  1243. # storage category
  1244. storage_category = self.env['stock.storage.category'].create({
  1245. 'name': "storage category"
  1246. })
  1247. package_type = self.env['stock.package.type'].create({
  1248. 'name': "package type",
  1249. })
  1250. # set the capacity for the package type in this storage category to be 1
  1251. storage_category_form = Form(storage_category, view='stock.stock_storage_category_form')
  1252. with storage_category_form.package_capacity_ids.new() as line:
  1253. line.package_type_id = package_type
  1254. line.quantity = 1
  1255. storage_category = storage_category_form.save()
  1256. self.env['stock.location'].create({
  1257. 'name': 'shelf1',
  1258. 'usage': 'internal',
  1259. 'location_id': self.stock_location.id,
  1260. })
  1261. shelf2_location = self.env['stock.location'].create({
  1262. 'name': 'shelf2',
  1263. 'usage': 'internal',
  1264. 'location_id': self.stock_location.id,
  1265. 'storage_category_id': storage_category.id,
  1266. })
  1267. # putaway from stock to child location with storage_category
  1268. putaway = self.env['stock.putaway.rule'].create({
  1269. 'product_id': self.product.id,
  1270. 'location_in_id': self.stock_location.id,
  1271. 'location_out_id': self.stock_location.id,
  1272. 'storage_category_id': storage_category.id,
  1273. 'package_type_ids': [(4, package_type.id, 0)],
  1274. })
  1275. self.stock_location.write({
  1276. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1277. })
  1278. # first package
  1279. package1 = self.env['stock.quant.package'].create({
  1280. 'name': 'package 1',
  1281. 'package_type_id': package_type.id,
  1282. })
  1283. move1 = self.env['stock.move'].create({
  1284. 'name': 'test_move_1',
  1285. 'location_id': self.supplier_location.id,
  1286. 'location_dest_id': self.stock_location.id,
  1287. 'product_id': self.product.id,
  1288. 'product_uom': self.uom_unit.id,
  1289. 'product_uom_qty': 100.0,
  1290. })
  1291. move1._action_confirm()
  1292. self.assertEqual(move1.state, 'assigned')
  1293. self.assertEqual(len(move1.move_line_ids), 1)
  1294. move_form = Form(move1, view='stock.view_stock_move_nosuggest_operations')
  1295. with move_form.move_line_nosuggest_ids.new() as line:
  1296. line.result_package_id = package1
  1297. line.qty_done = 100
  1298. move1 = move_form.save()
  1299. move1._action_done()
  1300. # check if the putaway was rightly applied
  1301. self.assertEqual(package1.location_id.id, shelf2_location.id)
  1302. # second package
  1303. package2 = self.env['stock.quant.package'].create({
  1304. 'name': 'package 2',
  1305. 'package_type_id': package_type.id,
  1306. })
  1307. move2 = self.env['stock.move'].create({
  1308. 'name': 'test_move_2',
  1309. 'location_id': self.supplier_location.id,
  1310. 'location_dest_id': self.stock_location.id,
  1311. 'product_id': self.product.id,
  1312. 'product_uom': self.uom_unit.id,
  1313. 'product_uom_qty': 100.0,
  1314. })
  1315. move2._action_confirm()
  1316. self.assertEqual(move2.state, 'assigned')
  1317. self.assertEqual(len(move2.move_line_ids), 1)
  1318. move_form = Form(move2, view='stock.view_stock_move_nosuggest_operations')
  1319. with move_form.move_line_nosuggest_ids.new() as line:
  1320. line.result_package_id = package2
  1321. line.qty_done = 100
  1322. move2 = move_form.save()
  1323. move2._action_done()
  1324. # check if the putaway wasn't applied
  1325. self.assertEqual(package2.location_id.id, self.stock_location.id)
  1326. def test_putaway_with_storage_category_7(self):
  1327. """Receive package with same package type twice, set storage category to
  1328. only accept new product when empty. Check putaway rule can be applied on
  1329. the first one but not the second one.
  1330. """
  1331. # Required for `result_package_id` to be visible in the view
  1332. self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
  1333. # storage category
  1334. storage_category = self.env['stock.storage.category'].create({
  1335. 'name': "storage category",
  1336. 'allow_new_product': "empty",
  1337. })
  1338. package_type = self.env['stock.package.type'].create({
  1339. 'name': "package type",
  1340. })
  1341. # set the capacity for the package type in this storage category to be 100
  1342. storage_category_form = Form(storage_category, view='stock.stock_storage_category_form')
  1343. with storage_category_form.package_capacity_ids.new() as line:
  1344. line.package_type_id = package_type
  1345. line.quantity = 100
  1346. storage_category = storage_category_form.save()
  1347. self.env['stock.location'].create({
  1348. 'name': 'shelf1',
  1349. 'usage': 'internal',
  1350. 'location_id': self.stock_location.id,
  1351. })
  1352. shelf2_location = self.env['stock.location'].create({
  1353. 'name': 'shelf2',
  1354. 'usage': 'internal',
  1355. 'location_id': self.stock_location.id,
  1356. 'storage_category_id': storage_category.id,
  1357. })
  1358. # putaway from stock to child location with storage_category
  1359. putaway = self.env['stock.putaway.rule'].create({
  1360. 'product_id': self.product.id,
  1361. 'location_in_id': self.stock_location.id,
  1362. 'location_out_id': self.stock_location.id,
  1363. 'storage_category_id': storage_category.id,
  1364. 'package_type_ids': [(4, package_type.id, 0)],
  1365. })
  1366. self.stock_location.write({
  1367. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1368. })
  1369. # first package
  1370. package1 = self.env['stock.quant.package'].create({
  1371. 'name': 'package 1',
  1372. 'package_type_id': package_type.id,
  1373. })
  1374. move1 = self.env['stock.move'].create({
  1375. 'name': 'test_move_1',
  1376. 'location_id': self.supplier_location.id,
  1377. 'location_dest_id': self.stock_location.id,
  1378. 'product_id': self.product.id,
  1379. 'product_uom': self.uom_unit.id,
  1380. 'product_uom_qty': 100.0,
  1381. })
  1382. move1._action_confirm()
  1383. self.assertEqual(move1.state, 'assigned')
  1384. self.assertEqual(len(move1.move_line_ids), 1)
  1385. move_form = Form(move1, view='stock.view_stock_move_nosuggest_operations')
  1386. with move_form.move_line_nosuggest_ids.new() as line:
  1387. line.result_package_id = package1
  1388. line.qty_done = 100
  1389. move1 = move_form.save()
  1390. move1._action_done()
  1391. # check if the putaway was rightly applied
  1392. self.assertEqual(package1.location_id.id, shelf2_location.id)
  1393. # second package
  1394. package2 = self.env['stock.quant.package'].create({
  1395. 'name': 'package 2',
  1396. 'package_type_id': package_type.id,
  1397. })
  1398. move2 = self.env['stock.move'].create({
  1399. 'name': 'test_move_2',
  1400. 'location_id': self.supplier_location.id,
  1401. 'location_dest_id': self.stock_location.id,
  1402. 'product_id': self.product.id,
  1403. 'product_uom': self.uom_unit.id,
  1404. 'product_uom_qty': 100.0,
  1405. })
  1406. move2._action_confirm()
  1407. self.assertEqual(move2.state, 'assigned')
  1408. self.assertEqual(len(move2.move_line_ids), 1)
  1409. move_form = Form(move2, view='stock.view_stock_move_nosuggest_operations')
  1410. with move_form.move_line_nosuggest_ids.new() as line:
  1411. line.result_package_id = package2
  1412. line.qty_done = 100
  1413. move2 = move_form.save()
  1414. move2._action_done()
  1415. # check if the putaway wasn't applied
  1416. self.assertEqual(package2.location_id.id, self.stock_location.id)
  1417. def test_putaway_with_storage_category_8(self):
  1418. """Receive package withs different products, set storage category to only
  1419. accept same product. Check putaway rule can be applied on the first one
  1420. but not the second one.
  1421. """
  1422. # Required for `result_package_id` to be visible in the view
  1423. self.env.user.groups_id += self.env.ref("stock.group_tracking_lot")
  1424. # storage category
  1425. storage_category = self.env['stock.storage.category'].create({
  1426. 'name': "storage category",
  1427. 'allow_new_product': "same",
  1428. })
  1429. package_type = self.env['stock.package.type'].create({
  1430. 'name': "package type",
  1431. })
  1432. # set the capacity for the package type in this storage category to be 100
  1433. storage_category_form = Form(storage_category, view='stock.stock_storage_category_form')
  1434. with storage_category_form.package_capacity_ids.new() as line:
  1435. line.package_type_id = package_type
  1436. line.quantity = 100
  1437. storage_category = storage_category_form.save()
  1438. self.env['stock.location'].create({
  1439. 'name': 'shelf1',
  1440. 'usage': 'internal',
  1441. 'location_id': self.stock_location.id,
  1442. })
  1443. shelf2_location = self.env['stock.location'].create({
  1444. 'name': 'shelf2',
  1445. 'usage': 'internal',
  1446. 'location_id': self.stock_location.id,
  1447. 'storage_category_id': storage_category.id,
  1448. })
  1449. # putaway from stock to child location for package type
  1450. putaway = self.env['stock.putaway.rule'].create({
  1451. 'location_in_id': self.stock_location.id,
  1452. 'location_out_id': self.stock_location.id,
  1453. 'storage_category_id': storage_category.id,
  1454. 'package_type_ids': [(4, package_type.id, 0)],
  1455. })
  1456. self.stock_location.write({
  1457. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1458. })
  1459. # first package
  1460. package1 = self.env['stock.quant.package'].create({
  1461. 'name': 'package 1',
  1462. 'package_type_id': package_type.id,
  1463. })
  1464. move1 = self.env['stock.move'].create({
  1465. 'name': 'test_move_1',
  1466. 'location_id': self.supplier_location.id,
  1467. 'location_dest_id': self.stock_location.id,
  1468. 'product_id': self.product.id,
  1469. 'product_uom': self.uom_unit.id,
  1470. 'product_uom_qty': 100.0,
  1471. })
  1472. move1._action_confirm()
  1473. self.assertEqual(move1.state, 'assigned')
  1474. self.assertEqual(len(move1.move_line_ids), 1)
  1475. move_form = Form(move1, view='stock.view_stock_move_nosuggest_operations')
  1476. with move_form.move_line_nosuggest_ids.new() as line:
  1477. line.result_package_id = package1
  1478. line.qty_done = 100
  1479. move1 = move_form.save()
  1480. move1._action_done()
  1481. # check if the putaway was rightly applied
  1482. self.assertEqual(package1.location_id.id, shelf2_location.id)
  1483. # second package
  1484. package2 = self.env['stock.quant.package'].create({
  1485. 'name': 'package 2',
  1486. 'package_type_id': package_type.id,
  1487. })
  1488. product2 = self.env['product.product'].create({
  1489. 'name': 'Product 2',
  1490. 'type': 'product',
  1491. 'categ_id': self.env.ref('product.product_category_all').id,
  1492. })
  1493. move2 = self.env['stock.move'].create({
  1494. 'name': 'test_move_2',
  1495. 'location_id': self.supplier_location.id,
  1496. 'location_dest_id': self.stock_location.id,
  1497. 'product_id': product2.id,
  1498. 'product_uom': self.uom_unit.id,
  1499. 'product_uom_qty': 100.0,
  1500. })
  1501. move2._action_confirm()
  1502. self.assertEqual(move2.state, 'assigned')
  1503. self.assertEqual(len(move2.move_line_ids), 1)
  1504. move_form = Form(move2, view='stock.view_stock_move_nosuggest_operations')
  1505. with move_form.move_line_nosuggest_ids.new() as line:
  1506. line.result_package_id = package2
  1507. line.qty_done = 100
  1508. move2 = move_form.save()
  1509. move2._action_done()
  1510. # check if the putaway wasn't applied
  1511. self.assertEqual(package2.location_id.id, self.stock_location.id)
  1512. def test_putaway_with_storage_category_9(self):
  1513. """Receive a product twice. Test first time the putaway applied, and second
  1514. time it is not since the products violate the max_weight limitaion.
  1515. """
  1516. self.product.weight = 1
  1517. storage_category = self.env['stock.storage.category'].create({
  1518. 'name': "storage category",
  1519. 'max_weight': 100,
  1520. })
  1521. shelf1_location = self.env['stock.location'].create({
  1522. 'name': 'shelf1',
  1523. 'usage': 'internal',
  1524. 'location_id': self.stock_location.id,
  1525. 'storage_category_id': storage_category.id,
  1526. })
  1527. # putaway from stock to child location with storage_category
  1528. putaway = self.env['stock.putaway.rule'].create({
  1529. 'product_id': self.product.id,
  1530. 'location_in_id': self.stock_location.id,
  1531. 'location_out_id': self.stock_location.id,
  1532. 'storage_category_id': storage_category.id,
  1533. })
  1534. self.stock_location.write({
  1535. 'putaway_rule_ids': [(4, putaway.id, 0)],
  1536. })
  1537. # first move
  1538. move1 = self.env['stock.move'].create({
  1539. 'name': 'test_move_1',
  1540. 'location_id': self.supplier_location.id,
  1541. 'location_dest_id': self.stock_location.id,
  1542. 'product_id': self.product.id,
  1543. 'product_uom': self.uom_unit.id,
  1544. 'product_uom_qty': 100.0,
  1545. })
  1546. move1._action_confirm()
  1547. self.assertEqual(move1.state, 'assigned')
  1548. self.assertEqual(len(move1.move_line_ids), 1)
  1549. # check if the putaway was rightly applied
  1550. self.assertEqual(move1.move_line_ids.location_dest_id.id, shelf1_location.id)
  1551. # second move
  1552. move2 = self.env['stock.move'].create({
  1553. 'name': 'test_move_2',
  1554. 'location_id': self.supplier_location.id,
  1555. 'location_dest_id': self.stock_location.id,
  1556. 'product_id': self.product.id,
  1557. 'product_uom': self.uom_unit.id,
  1558. 'product_uom_qty': 100.0,
  1559. })
  1560. move2._action_confirm()
  1561. self.assertEqual(move1.state, 'assigned')
  1562. self.assertEqual(len(move2.move_line_ids), 1)
  1563. # check if the putaway wasn't applied since there are already 100kg products in the location
  1564. self.assertEqual(move2.move_line_ids.location_dest_id.id, self.stock_location.id)
  1565. def test_availability_1(self):
  1566. """ Check that the `availability` field on a move is correctly computed when there is
  1567. more than enough products in stock.
  1568. """
  1569. # make some stock
  1570. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 150.0)
  1571. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  1572. move1 = self.env['stock.move'].create({
  1573. 'name': 'test_putaway_1',
  1574. 'location_id': self.stock_location.id,
  1575. 'location_dest_id': self.supplier_location.id,
  1576. 'product_id': self.product.id,
  1577. 'product_uom': self.uom_unit.id,
  1578. 'product_uom_qty': 100.0,
  1579. })
  1580. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 150.0)
  1581. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  1582. self.assertEqual(move1.availability, 100.0)
  1583. def test_availability_2(self):
  1584. """ Check that the `availability` field on a move is correctly computed when there is
  1585. not enough products in stock.
  1586. """
  1587. # make some stock
  1588. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 50.0)
  1589. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  1590. move1 = self.env['stock.move'].create({
  1591. 'name': 'test_putaway_1',
  1592. 'location_id': self.stock_location.id,
  1593. 'location_dest_id': self.supplier_location.id,
  1594. 'product_id': self.product.id,
  1595. 'product_uom': self.uom_unit.id,
  1596. 'product_uom_qty': 100.0,
  1597. })
  1598. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 50.0)
  1599. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  1600. self.assertEqual(move1.availability, 50.0)
  1601. def test_availability_3(self):
  1602. lot1 = self.env['stock.lot'].create({
  1603. 'name': 'lot1',
  1604. 'product_id': self.product_serial.id,
  1605. 'company_id': self.env.company.id,
  1606. })
  1607. lot2 = self.env['stock.lot'].create({
  1608. 'name': 'lot2',
  1609. 'product_id': self.product_serial.id,
  1610. 'company_id': self.env.company.id,
  1611. })
  1612. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, -1.0, lot_id=lot1)
  1613. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1.0, lot_id=lot2)
  1614. move1 = self.env['stock.move'].create({
  1615. 'name': 'test_availability_3',
  1616. 'location_id': self.stock_location.id,
  1617. 'location_dest_id': self.customer_location.id,
  1618. 'product_id': self.product_serial.id,
  1619. 'product_uom': self.uom_unit.id,
  1620. 'product_uom_qty': 1.0,
  1621. })
  1622. move1._action_confirm()
  1623. move1._action_assign()
  1624. self.assertEqual(move1.state, 'assigned')
  1625. self.assertEqual(move1.reserved_availability, 1.0)
  1626. def test_availability_4(self):
  1627. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 30.0)
  1628. move1 = self.env['stock.move'].create({
  1629. 'name': 'test_availability_4',
  1630. 'location_id': self.stock_location.id,
  1631. 'location_dest_id': self.customer_location.id,
  1632. 'product_id': self.product.id,
  1633. 'product_uom': self.uom_unit.id,
  1634. 'product_uom_qty': 15.0,
  1635. })
  1636. move1._action_confirm()
  1637. move1._action_assign()
  1638. self.assertEqual(move1.state, 'assigned')
  1639. move2 = self.env['stock.move'].create({
  1640. 'name': 'test_availability_4',
  1641. 'location_id': self.stock_location.id,
  1642. 'location_dest_id': self.customer_location.id,
  1643. 'product_id': self.product.id,
  1644. 'product_uom': self.uom_unit.id,
  1645. 'product_uom_qty': 15.0,
  1646. })
  1647. move2._action_confirm()
  1648. move2._action_assign()
  1649. # set 15 as quantity done for the first and 30 as the second
  1650. move1.move_line_ids.qty_done = 15
  1651. move2.move_line_ids.qty_done = 30
  1652. # validate the second, the first should be unreserved
  1653. move2._action_done()
  1654. self.assertEqual(move1.state, 'confirmed')
  1655. self.assertEqual(move1.move_line_ids.qty_done, 15)
  1656. self.assertEqual(move2.state, 'done')
  1657. stock_quants = self.gather_relevant(self.product, self.stock_location)
  1658. self.assertEqual(len(stock_quants), 0)
  1659. customer_quants = self.gather_relevant(self.product, self.customer_location)
  1660. self.assertEqual(customer_quants.quantity, 30)
  1661. self.assertEqual(customer_quants.reserved_quantity, 0)
  1662. def test_availability_5(self):
  1663. """ Check that rerun action assign only create new stock move
  1664. lines instead of adding quantity in existing one.
  1665. """
  1666. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 2.0)
  1667. # move from shelf1
  1668. move = self.env['stock.move'].create({
  1669. 'name': 'test_edit_moveline_1',
  1670. 'location_id': self.stock_location.id,
  1671. 'location_dest_id': self.customer_location.id,
  1672. 'product_id': self.product_serial.id,
  1673. 'product_uom': self.uom_unit.id,
  1674. 'product_uom_qty': 4.0,
  1675. })
  1676. move._action_confirm()
  1677. move._action_assign()
  1678. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 4.0)
  1679. move._action_assign()
  1680. self.assertEqual(len(move.move_line_ids), 4.0)
  1681. def test_availability_6(self):
  1682. """ Check that, in the scenario where a move is in a bigger uom than the uom of the quants
  1683. and this uom only allows entire numbers, we don't make a partial reservation when the
  1684. quantity available is not enough to reserve the move. Check also that it is not possible
  1685. to set `quantity_done` with a value not honouring the UOM's rounding.
  1686. """
  1687. # on the dozen uom, set the rounding set 1.0
  1688. self.uom_dozen.rounding = 1
  1689. # 6 units are available in stock
  1690. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 6.0)
  1691. # the move should not be reserved
  1692. move = self.env['stock.move'].create({
  1693. 'name': 'test_availability_6',
  1694. 'location_id': self.stock_location.id,
  1695. 'location_dest_id': self.customer_location.id,
  1696. 'product_id': self.product.id,
  1697. 'product_uom': self.uom_dozen.id,
  1698. 'product_uom_qty': 1,
  1699. })
  1700. move._action_confirm()
  1701. move._action_assign()
  1702. self.assertEqual(move.state, 'confirmed')
  1703. # the quants should be left untouched
  1704. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 6.0)
  1705. # make 8 units available, the move should again not be reservabale
  1706. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2.0)
  1707. move._action_assign()
  1708. self.assertEqual(move.state, 'confirmed')
  1709. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 8.0)
  1710. # make 12 units available, this time the move should be reservable
  1711. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 4.0)
  1712. move._action_assign()
  1713. self.assertEqual(move.state, 'assigned')
  1714. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  1715. # Check it isn't possible to set any value to quantity_done
  1716. with self.assertRaises(UserError):
  1717. move.quantity_done = 0.1
  1718. move._action_done()
  1719. with self.assertRaises(UserError):
  1720. move.quantity_done = 1.1
  1721. move._action_done()
  1722. with self.assertRaises(UserError):
  1723. move.quantity_done = 0.9
  1724. move._action_done()
  1725. move.quantity_done = 1
  1726. move._action_done()
  1727. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 12.0)
  1728. def test_availability_7(self):
  1729. """ Check that, in the scenario where a move is in a bigger uom than the uom of the quants
  1730. and this uom only allows entire numbers, we only reserve quantity honouring the uom's
  1731. rounding even if the quantity is set across multiple quants.
  1732. """
  1733. # on the dozen uom, set the rounding set 1.0
  1734. self.uom_dozen.rounding = 1
  1735. # make 12 quants of 1
  1736. for i in range(1, 13):
  1737. lot_id = self.env['stock.lot'].create({
  1738. 'name': 'lot%s' % str(i),
  1739. 'product_id': self.product_serial.id,
  1740. 'company_id': self.env.company.id,
  1741. })
  1742. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1.0, lot_id=lot_id)
  1743. # the move should be reserved
  1744. move = self.env['stock.move'].create({
  1745. 'name': 'test_availability_7',
  1746. 'location_id': self.stock_location.id,
  1747. 'location_dest_id': self.customer_location.id,
  1748. 'product_id': self.product_serial.id,
  1749. 'product_uom': self.uom_dozen.id,
  1750. 'product_uom_qty': 1,
  1751. })
  1752. move._action_confirm()
  1753. move._action_assign()
  1754. self.assertEqual(move.state, 'assigned')
  1755. self.assertEqual(len(move.move_line_ids.mapped('product_uom_id')), 1)
  1756. self.assertEqual(move.move_line_ids.mapped('product_uom_id'), self.uom_unit)
  1757. for move_line in move.move_line_ids:
  1758. move_line.qty_done = 1
  1759. move._action_done()
  1760. self.assertEqual(move.product_uom_qty, 1)
  1761. self.assertEqual(move.product_uom.id, self.uom_dozen.id)
  1762. self.assertEqual(move.state, 'done')
  1763. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.customer_location), 12.0)
  1764. self.assertEqual(len(self.gather_relevant(self.product_serial, self.customer_location)), 12)
  1765. def test_availability_8(self):
  1766. """ Test the assignment mechanism when the product quantity is decreased on a partially
  1767. reserved stock move.
  1768. """
  1769. # make some stock
  1770. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 3.0)
  1771. self.assertAlmostEqual(self.product.qty_available, 3.0)
  1772. move_partial = self.env['stock.move'].create({
  1773. 'name': 'test_partial',
  1774. 'location_id': self.stock_location.id,
  1775. 'location_dest_id': self.customer_location.id,
  1776. 'product_id': self.product.id,
  1777. 'product_uom': self.uom_unit.id,
  1778. 'product_uom_qty': 5.0,
  1779. })
  1780. move_partial._action_confirm()
  1781. move_partial._action_assign()
  1782. self.assertAlmostEqual(self.product.virtual_available, -2.0)
  1783. self.assertEqual(move_partial.state, 'partially_available')
  1784. move_partial.product_uom_qty = 3.0
  1785. move_partial._action_assign()
  1786. self.assertEqual(move_partial.state, 'assigned')
  1787. def test_availability_9(self):
  1788. """ Test the assignment mechanism when the product quantity is increase
  1789. on a receipt move.
  1790. """
  1791. move_receipt = self.env['stock.move'].create({
  1792. 'name': 'test_receipt_edit',
  1793. 'location_id': self.supplier_location.id,
  1794. 'location_dest_id': self.stock_location.id,
  1795. 'product_id': self.product.id,
  1796. 'product_uom': self.uom_dozen.id,
  1797. 'product_uom_qty': 1.0,
  1798. })
  1799. move_receipt._action_confirm()
  1800. move_receipt._action_assign()
  1801. self.assertEqual(move_receipt.state, 'assigned')
  1802. move_receipt.product_uom_qty = 3.0
  1803. move_receipt._action_assign()
  1804. self.assertEqual(move_receipt.state, 'assigned')
  1805. self.assertEqual(move_receipt.move_line_ids.reserved_uom_qty, 3)
  1806. def test_unreserve_1(self):
  1807. """ Check that unreserving a stock move sets the products reserved as available and
  1808. set the state back to confirmed.
  1809. """
  1810. # make some stock
  1811. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 150.0)
  1812. # creation
  1813. move1 = self.env['stock.move'].create({
  1814. 'name': 'test_putaway_1',
  1815. 'location_id': self.stock_location.id,
  1816. 'location_dest_id': self.supplier_location.id,
  1817. 'product_id': self.product.id,
  1818. 'product_uom': self.uom_unit.id,
  1819. 'product_uom_qty': 100.0,
  1820. })
  1821. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 150.0)
  1822. self.assertEqual(move1.availability, 100.0)
  1823. # confirmation
  1824. move1._action_confirm()
  1825. self.assertEqual(move1.state, 'confirmed')
  1826. # assignment
  1827. move1._action_assign()
  1828. self.assertEqual(move1.state, 'assigned')
  1829. self.assertEqual(len(move1.move_line_ids), 1)
  1830. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 50.0)
  1831. # unreserve
  1832. move1._do_unreserve()
  1833. self.assertEqual(len(move1.move_line_ids), 0)
  1834. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 150.0)
  1835. self.assertEqual(move1.state, 'confirmed')
  1836. def test_unreserve_2(self):
  1837. """ Check that unreserving a stock move sets the products reserved as available and
  1838. set the state back to confirmed even if they are in a pack.
  1839. """
  1840. package1 = self.env['stock.quant.package'].create({'name': 'test_unreserve_2_pack'})
  1841. # make some stock
  1842. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 150.0, package_id=package1)
  1843. # creation
  1844. move1 = self.env['stock.move'].create({
  1845. 'name': 'test_putaway_1',
  1846. 'location_id': self.stock_location.id,
  1847. 'location_dest_id': self.supplier_location.id,
  1848. 'product_id': self.product.id,
  1849. 'product_uom': self.uom_unit.id,
  1850. 'product_uom_qty': 100.0,
  1851. })
  1852. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 150.0)
  1853. self.assertEqual(move1.availability, 100.0)
  1854. # confirmation
  1855. move1._action_confirm()
  1856. self.assertEqual(move1.state, 'confirmed')
  1857. # assignment
  1858. move1._action_assign()
  1859. self.assertEqual(move1.state, 'assigned')
  1860. self.assertEqual(len(move1.move_line_ids), 1)
  1861. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 50.0)
  1862. # unreserve
  1863. move1._do_unreserve()
  1864. self.assertEqual(len(move1.move_line_ids), 0)
  1865. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 150.0)
  1866. self.assertEqual(move1.state, 'confirmed')
  1867. def test_unreserve_3(self):
  1868. """ Similar to `test_unreserve_1` but checking the quants more in details.
  1869. """
  1870. # make some stock
  1871. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2)
  1872. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  1873. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2)
  1874. # creation
  1875. move1 = self.env['stock.move'].create({
  1876. 'name': 'test_out_1',
  1877. 'location_id': self.stock_location.id,
  1878. 'location_dest_id': self.customer_location.id,
  1879. 'product_id': self.product.id,
  1880. 'product_uom': self.uom_unit.id,
  1881. 'product_uom_qty': 2.0,
  1882. })
  1883. self.assertEqual(move1.state, 'draft')
  1884. # confirmation
  1885. move1._action_confirm()
  1886. self.assertEqual(move1.state, 'confirmed')
  1887. # assignment
  1888. move1._action_assign()
  1889. self.assertEqual(move1.state, 'assigned')
  1890. self.assertEqual(len(move1.move_line_ids), 1)
  1891. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  1892. quants = self.gather_relevant(self.product, self.stock_location)
  1893. self.assertEqual(len(quants), 1.0)
  1894. self.assertEqual(quants.quantity, 2.0)
  1895. self.assertEqual(quants.reserved_quantity, 2.0)
  1896. move1._do_unreserve()
  1897. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  1898. self.assertEqual(len(quants), 1.0)
  1899. self.assertEqual(quants.quantity, 2.0)
  1900. self.assertEqual(quants.reserved_quantity, 0.0)
  1901. self.assertEqual(len(move1.move_line_ids), 0.0)
  1902. def test_unreserve_4(self):
  1903. """ Check the unreservation of a partially available stock move.
  1904. """
  1905. # make some stock
  1906. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2)
  1907. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  1908. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2)
  1909. # creation
  1910. move1 = self.env['stock.move'].create({
  1911. 'name': 'test_out_1',
  1912. 'location_id': self.stock_location.id,
  1913. 'location_dest_id': self.customer_location.id,
  1914. 'product_id': self.product.id,
  1915. 'product_uom': self.uom_unit.id,
  1916. 'product_uom_qty': 3.0,
  1917. })
  1918. self.assertEqual(move1.state, 'draft')
  1919. # confirmation
  1920. move1._action_confirm()
  1921. self.assertEqual(move1.state, 'confirmed')
  1922. # assignment
  1923. move1._action_assign()
  1924. self.assertEqual(move1.state, 'partially_available')
  1925. self.assertEqual(len(move1.move_line_ids), 1)
  1926. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  1927. quants = self.gather_relevant(self.product, self.stock_location)
  1928. self.assertEqual(len(quants), 1.0)
  1929. self.assertEqual(quants.quantity, 2.0)
  1930. self.assertEqual(quants.reserved_quantity, 2.0)
  1931. move1._do_unreserve()
  1932. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  1933. self.assertEqual(len(quants), 1.0)
  1934. self.assertEqual(quants.quantity, 2.0)
  1935. self.assertEqual(quants.reserved_quantity, 0.0)
  1936. self.assertEqual(len(move1.move_line_ids), 0.0)
  1937. def test_unreserve_5(self):
  1938. """ Check the unreservation of a stock move reserved on multiple quants.
  1939. """
  1940. # make some stock
  1941. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 3)
  1942. self.env['stock.quant'].create({
  1943. 'product_id': self.product.id,
  1944. 'location_id': self.stock_location.id,
  1945. 'quantity': 2,
  1946. })
  1947. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 2)
  1948. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 5)
  1949. # creation
  1950. move1 = self.env['stock.move'].create({
  1951. 'name': 'test_unreserve_5',
  1952. 'location_id': self.stock_location.id,
  1953. 'location_dest_id': self.customer_location.id,
  1954. 'product_id': self.product.id,
  1955. 'product_uom': self.uom_unit.id,
  1956. 'product_uom_qty': 5.0,
  1957. })
  1958. self.assertEqual(move1.state, 'draft')
  1959. # confirmation
  1960. move1._action_confirm()
  1961. self.assertEqual(move1.state, 'confirmed')
  1962. # assignment
  1963. move1._action_assign()
  1964. self.assertEqual(move1.state, 'assigned')
  1965. self.assertEqual(len(move1.move_line_ids), 1)
  1966. move1._do_unreserve()
  1967. quants = self.gather_relevant(self.product, self.stock_location)
  1968. self.assertEqual(len(quants), 2.0)
  1969. for quant in quants:
  1970. self.assertEqual(quant.reserved_quantity, 0)
  1971. def test_unreserve_6(self):
  1972. """ In a situation with a negative and a positive quant, reserve and unreserve.
  1973. """
  1974. q1 = self.env['stock.quant'].create({
  1975. 'product_id': self.product.id,
  1976. 'location_id': self.stock_location.id,
  1977. 'quantity': -10,
  1978. 'reserved_quantity': 0,
  1979. })
  1980. q2 = self.env['stock.quant'].create({
  1981. 'product_id': self.product.id,
  1982. 'location_id': self.stock_location.id,
  1983. 'quantity': 30.0,
  1984. 'reserved_quantity': 10.0,
  1985. })
  1986. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 10.0)
  1987. move1 = self.env['stock.move'].create({
  1988. 'name': 'test_unreserve_6',
  1989. 'location_id': self.stock_location.id,
  1990. 'location_dest_id': self.customer_location.id,
  1991. 'product_id': self.product.id,
  1992. 'product_uom': self.uom_unit.id,
  1993. 'product_uom_qty': 10.0,
  1994. })
  1995. move1._action_confirm()
  1996. move1._action_assign()
  1997. self.assertEqual(move1.state, 'assigned')
  1998. self.assertEqual(len(move1.move_line_ids), 1)
  1999. self.assertEqual(move1.move_line_ids.reserved_qty, 10)
  2000. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  2001. self.assertEqual(q2.reserved_quantity, 20)
  2002. move1._do_unreserve()
  2003. self.assertEqual(move1.state, 'confirmed')
  2004. self.assertEqual(len(move1.move_line_ids), 0)
  2005. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 10.0)
  2006. self.assertEqual(q2.reserved_quantity, 10)
  2007. def test_unreserve_7(self):
  2008. """ Check the unreservation of a stock move delete only stock move lines
  2009. without quantity done.
  2010. """
  2011. product = self.env['product.product'].create({
  2012. 'name': 'product',
  2013. 'tracking': 'serial',
  2014. 'type': 'product',
  2015. })
  2016. serial_numbers = self.env['stock.lot'].create([{
  2017. 'name': str(x),
  2018. 'product_id': product.id,
  2019. 'company_id': self.env.company.id,
  2020. } for x in range(5)])
  2021. for serial in serial_numbers:
  2022. self.env['stock.quant'].create({
  2023. 'product_id': product.id,
  2024. 'location_id': self.stock_location.id,
  2025. 'quantity': 1.0,
  2026. 'lot_id': serial.id,
  2027. 'reserved_quantity': 0.0,
  2028. })
  2029. move1 = self.env['stock.move'].create({
  2030. 'name': 'test_unreserve_7',
  2031. 'location_id': self.stock_location.id,
  2032. 'location_dest_id': self.customer_location.id,
  2033. 'product_id': product.id,
  2034. 'product_uom': product.uom_id.id,
  2035. 'product_uom_qty': 5.0,
  2036. })
  2037. move1._action_confirm()
  2038. move1._action_assign()
  2039. self.assertEqual(move1.state, 'assigned')
  2040. self.assertEqual(len(move1.move_line_ids), 5)
  2041. self.assertEqual(self.env['stock.quant']._get_available_quantity(product, self.stock_location), 0.0)
  2042. # Check state is changed even with 0 move lines unlinked
  2043. move1.move_line_ids.write({'qty_done': 1})
  2044. move1._do_unreserve()
  2045. self.assertEqual(len(move1.move_line_ids), 5)
  2046. self.assertEqual(move1.state, 'confirmed')
  2047. move1._action_assign()
  2048. # set a quantity done on the two first move lines
  2049. move1.move_line_ids.write({'qty_done': 0})
  2050. move1.move_line_ids[0].qty_done = 1
  2051. move1.move_line_ids[1].qty_done = 1
  2052. move1._do_unreserve()
  2053. self.assertEqual(move1.state, 'confirmed')
  2054. self.assertEqual(len(move1.move_line_ids), 2)
  2055. self.assertEqual(move1.move_line_ids.mapped('qty_done'), [1, 1])
  2056. self.assertEqual(move1.move_line_ids.mapped('reserved_uom_qty'), [0, 0])
  2057. def test_link_assign_1(self):
  2058. """ Test the assignment mechanism when two chained stock moves try to move one unit of an
  2059. untracked product.
  2060. """
  2061. # make some stock
  2062. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0)
  2063. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  2064. move_stock_pack = self.env['stock.move'].create({
  2065. 'name': 'test_link_assign_1_1',
  2066. 'location_id': self.stock_location.id,
  2067. 'location_dest_id': self.pack_location.id,
  2068. 'product_id': self.product.id,
  2069. 'product_uom': self.uom_unit.id,
  2070. 'product_uom_qty': 1.0,
  2071. })
  2072. move_pack_cust = self.env['stock.move'].create({
  2073. 'name': 'test_link_assign_1_2',
  2074. 'location_id': self.pack_location.id,
  2075. 'location_dest_id': self.customer_location.id,
  2076. 'product_id': self.product.id,
  2077. 'product_uom': self.uom_unit.id,
  2078. 'product_uom_qty': 1.0,
  2079. })
  2080. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2081. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2082. (move_stock_pack + move_pack_cust)._action_confirm()
  2083. move_stock_pack._action_assign()
  2084. move_stock_pack.move_line_ids[0].qty_done = 1.0
  2085. move_stock_pack._action_done()
  2086. self.assertEqual(len(move_pack_cust.move_line_ids), 1)
  2087. move_line = move_pack_cust.move_line_ids[0]
  2088. self.assertEqual(move_line.location_id.id, self.pack_location.id)
  2089. self.assertEqual(move_line.location_dest_id.id, self.customer_location.id)
  2090. self.assertEqual(move_pack_cust.state, 'assigned')
  2091. def test_link_assign_2(self):
  2092. """ Test the assignment mechanism when two chained stock moves try to move one unit of a
  2093. tracked product.
  2094. """
  2095. lot1 = self.env['stock.lot'].create({
  2096. 'name': 'lot1',
  2097. 'product_id': self.product.id,
  2098. 'company_id': self.env.company.id,
  2099. })
  2100. # make some stock
  2101. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot1)
  2102. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location, lot1)), 1.0)
  2103. move_stock_pack = self.env['stock.move'].create({
  2104. 'name': 'test_link_2_1',
  2105. 'location_id': self.stock_location.id,
  2106. 'location_dest_id': self.pack_location.id,
  2107. 'product_id': self.product.id,
  2108. 'product_uom': self.uom_unit.id,
  2109. 'product_uom_qty': 1.0,
  2110. })
  2111. move_pack_cust = self.env['stock.move'].create({
  2112. 'name': 'test_link_2_2',
  2113. 'location_id': self.pack_location.id,
  2114. 'location_dest_id': self.customer_location.id,
  2115. 'product_id': self.product.id,
  2116. 'product_uom': self.uom_unit.id,
  2117. 'product_uom_qty': 1.0,
  2118. })
  2119. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2120. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2121. (move_stock_pack + move_pack_cust)._action_confirm()
  2122. move_stock_pack._action_assign()
  2123. move_line_stock_pack = move_stock_pack.move_line_ids[0]
  2124. self.assertEqual(move_line_stock_pack.lot_id.id, lot1.id)
  2125. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 0.0)
  2126. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location, lot1)), 1.0)
  2127. self.assertEqual(len(self.gather_relevant(self.product, self.pack_location, lot1)), 0.0)
  2128. move_line_stock_pack.qty_done = 1.0
  2129. move_stock_pack._action_done()
  2130. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 0.0)
  2131. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location, lot1)), 0.0)
  2132. move_line_pack_cust = move_pack_cust.move_line_ids[0]
  2133. self.assertEqual(move_line_pack_cust.lot_id.id, lot1.id)
  2134. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.pack_location, lot_id=lot1), 0.0)
  2135. self.assertEqual(len(self.gather_relevant(self.product, self.pack_location, lot1)), 1.0)
  2136. def test_link_assign_3(self):
  2137. """ Test the assignment mechanism when three chained stock moves (2 sources, 1 dest) try to
  2138. move multiple units of an untracked product.
  2139. """
  2140. # make some stock
  2141. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2.0)
  2142. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  2143. move_stock_pack_1 = self.env['stock.move'].create({
  2144. 'name': 'test_link_assign_1_1',
  2145. 'location_id': self.stock_location.id,
  2146. 'location_dest_id': self.pack_location.id,
  2147. 'product_id': self.product.id,
  2148. 'product_uom': self.uom_unit.id,
  2149. 'product_uom_qty': 1.0,
  2150. })
  2151. move_stock_pack_2 = self.env['stock.move'].create({
  2152. 'name': 'test_link_assign_1_1',
  2153. 'location_id': self.stock_location.id,
  2154. 'location_dest_id': self.pack_location.id,
  2155. 'product_id': self.product.id,
  2156. 'product_uom': self.uom_unit.id,
  2157. 'product_uom_qty': 1.0,
  2158. })
  2159. move_pack_cust = self.env['stock.move'].create({
  2160. 'name': 'test_link_assign_1_2',
  2161. 'location_id': self.pack_location.id,
  2162. 'location_dest_id': self.customer_location.id,
  2163. 'product_id': self.product.id,
  2164. 'product_uom': self.uom_unit.id,
  2165. 'product_uom_qty': 2.0,
  2166. })
  2167. move_stock_pack_1.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2168. move_stock_pack_2.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2169. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack_1.id, 0), (4, move_stock_pack_2.id, 0)]})
  2170. (move_stock_pack_1 + move_stock_pack_2 + move_pack_cust)._action_confirm()
  2171. # assign and fulfill the first move
  2172. move_stock_pack_1._action_assign()
  2173. self.assertEqual(move_stock_pack_1.state, 'assigned')
  2174. self.assertEqual(len(move_stock_pack_1.move_line_ids), 1)
  2175. move_stock_pack_1.move_line_ids[0].qty_done = 1.0
  2176. move_stock_pack_1._action_done()
  2177. self.assertEqual(move_stock_pack_1.state, 'done')
  2178. # the destination move should be partially available and have one move line
  2179. self.assertEqual(move_pack_cust.state, 'partially_available')
  2180. self.assertEqual(len(move_pack_cust.move_line_ids), 1)
  2181. # Should have 1 quant in stock_location and another in pack_location
  2182. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  2183. self.assertEqual(len(self.gather_relevant(self.product, self.pack_location)), 1.0)
  2184. move_stock_pack_2._action_assign()
  2185. self.assertEqual(move_stock_pack_2.state, 'assigned')
  2186. self.assertEqual(len(move_stock_pack_2.move_line_ids), 1)
  2187. move_stock_pack_2.move_line_ids[0].qty_done = 1.0
  2188. move_stock_pack_2._action_done()
  2189. self.assertEqual(move_stock_pack_2.state, 'done')
  2190. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  2191. self.assertEqual(len(self.gather_relevant(self.product, self.pack_location)), 1.0)
  2192. self.assertEqual(move_pack_cust.state, 'assigned')
  2193. self.assertEqual(len(move_pack_cust.move_line_ids), 1)
  2194. move_line_1 = move_pack_cust.move_line_ids[0]
  2195. self.assertEqual(move_line_1.location_id.id, self.pack_location.id)
  2196. self.assertEqual(move_line_1.location_dest_id.id, self.customer_location.id)
  2197. self.assertEqual(move_line_1.reserved_qty, 2.0)
  2198. self.assertEqual(move_pack_cust.state, 'assigned')
  2199. def test_link_assign_4(self):
  2200. """ Test the assignment mechanism when three chained stock moves (2 sources, 1 dest) try to
  2201. move multiple units of a tracked by lot product.
  2202. """
  2203. lot1 = self.env['stock.lot'].create({
  2204. 'name': 'lot1',
  2205. 'product_id': self.product.id,
  2206. 'company_id': self.env.company.id,
  2207. })
  2208. # make some stock
  2209. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2.0, lot_id=lot1)
  2210. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location, lot1)), 1.0)
  2211. move_stock_pack_1 = self.env['stock.move'].create({
  2212. 'name': 'test_link_assign_1_1',
  2213. 'location_id': self.stock_location.id,
  2214. 'location_dest_id': self.pack_location.id,
  2215. 'product_id': self.product.id,
  2216. 'product_uom': self.uom_unit.id,
  2217. 'product_uom_qty': 1.0,
  2218. })
  2219. move_stock_pack_2 = self.env['stock.move'].create({
  2220. 'name': 'test_link_assign_1_1',
  2221. 'location_id': self.stock_location.id,
  2222. 'location_dest_id': self.pack_location.id,
  2223. 'product_id': self.product.id,
  2224. 'product_uom': self.uom_unit.id,
  2225. 'product_uom_qty': 1.0,
  2226. })
  2227. move_pack_cust = self.env['stock.move'].create({
  2228. 'name': 'test_link_assign_1_2',
  2229. 'location_id': self.pack_location.id,
  2230. 'location_dest_id': self.customer_location.id,
  2231. 'product_id': self.product.id,
  2232. 'product_uom': self.uom_unit.id,
  2233. 'product_uom_qty': 2.0,
  2234. })
  2235. move_stock_pack_1.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2236. move_stock_pack_2.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2237. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack_1.id, 0), (4, move_stock_pack_2.id, 0)]})
  2238. (move_stock_pack_1 + move_stock_pack_2 + move_pack_cust)._action_confirm()
  2239. # assign and fulfill the first move
  2240. move_stock_pack_1._action_assign()
  2241. self.assertEqual(len(move_stock_pack_1.move_line_ids), 1)
  2242. self.assertEqual(move_stock_pack_1.move_line_ids[0].lot_id.id, lot1.id)
  2243. move_stock_pack_1.move_line_ids[0].qty_done = 1.0
  2244. move_stock_pack_1._action_done()
  2245. # the destination move should be partially available and have one move line
  2246. self.assertEqual(len(move_pack_cust.move_line_ids), 1)
  2247. move_stock_pack_2._action_assign()
  2248. self.assertEqual(len(move_stock_pack_2.move_line_ids), 1)
  2249. self.assertEqual(move_stock_pack_2.move_line_ids[0].lot_id.id, lot1.id)
  2250. move_stock_pack_2.move_line_ids[0].qty_done = 1.0
  2251. move_stock_pack_2._action_done()
  2252. self.assertEqual(len(move_pack_cust.move_line_ids), 1)
  2253. move_line_1 = move_pack_cust.move_line_ids[0]
  2254. self.assertEqual(move_line_1.location_id.id, self.pack_location.id)
  2255. self.assertEqual(move_line_1.location_dest_id.id, self.customer_location.id)
  2256. self.assertEqual(move_line_1.reserved_qty, 2.0)
  2257. self.assertEqual(move_line_1.lot_id.id, lot1.id)
  2258. self.assertEqual(move_pack_cust.state, 'assigned')
  2259. def test_link_assign_5(self):
  2260. """ Test the assignment mechanism when three chained stock moves (1 sources, 2 dest) try to
  2261. move multiple units of an untracked product.
  2262. """
  2263. # make some stock
  2264. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2.0)
  2265. move_stock_pack = self.env['stock.move'].create({
  2266. 'name': 'test_link_assign_1_1',
  2267. 'location_id': self.stock_location.id,
  2268. 'location_dest_id': self.pack_location.id,
  2269. 'product_id': self.product.id,
  2270. 'product_uom': self.uom_unit.id,
  2271. 'product_uom_qty': 2.0,
  2272. })
  2273. move_pack_cust_1 = self.env['stock.move'].create({
  2274. 'name': 'test_link_assign_1_1',
  2275. 'location_id': self.pack_location.id,
  2276. 'location_dest_id': self.customer_location.id,
  2277. 'product_id': self.product.id,
  2278. 'product_uom': self.uom_unit.id,
  2279. 'product_uom_qty': 1.0,
  2280. })
  2281. move_pack_cust_2 = self.env['stock.move'].create({
  2282. 'name': 'test_link_assign_1_2',
  2283. 'location_id': self.pack_location.id,
  2284. 'location_dest_id': self.customer_location.id,
  2285. 'product_id': self.product.id,
  2286. 'product_uom': self.uom_unit.id,
  2287. 'product_uom_qty': 1.0,
  2288. })
  2289. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust_1.id, 0), (4, move_pack_cust_2.id, 0)]})
  2290. move_pack_cust_1.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2291. move_pack_cust_2.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2292. (move_stock_pack + move_pack_cust_1 + move_pack_cust_2)._action_confirm()
  2293. # assign and fulfill the first move
  2294. move_stock_pack._action_assign()
  2295. self.assertEqual(len(move_stock_pack.move_line_ids), 1)
  2296. move_stock_pack.move_line_ids[0].qty_done = 2.0
  2297. move_stock_pack._action_done()
  2298. # the destination moves should be available and have one move line
  2299. self.assertEqual(len(move_pack_cust_1.move_line_ids), 1)
  2300. self.assertEqual(len(move_pack_cust_2.move_line_ids), 1)
  2301. move_pack_cust_1.move_line_ids[0].qty_done = 1.0
  2302. move_pack_cust_2.move_line_ids[0].qty_done = 1.0
  2303. (move_pack_cust_1 + move_pack_cust_2)._action_done()
  2304. def test_link_assign_6(self):
  2305. """ Test the assignment mechanism when four chained stock moves (2 sources, 2 dest) try to
  2306. move multiple units of an untracked by lot product. This particular test case simulates a two
  2307. step receipts with backorder.
  2308. """
  2309. move_supp_stock_1 = self.env['stock.move'].create({
  2310. 'name': 'test_link_assign_6_1',
  2311. 'location_id': self.supplier_location.id,
  2312. 'location_dest_id': self.stock_location.id,
  2313. 'product_id': self.product.id,
  2314. 'product_uom': self.uom_unit.id,
  2315. 'product_uom_qty': 3.0,
  2316. })
  2317. move_supp_stock_2 = self.env['stock.move'].create({
  2318. 'name': 'test_link_assign_6_1',
  2319. 'location_id': self.supplier_location.id,
  2320. 'location_dest_id': self.stock_location.id,
  2321. 'product_id': self.product.id,
  2322. 'product_uom': self.uom_unit.id,
  2323. 'product_uom_qty': 2.0,
  2324. })
  2325. move_stock_stock_1 = self.env['stock.move'].create({
  2326. 'name': 'test_link_assign_6_1',
  2327. 'location_id': self.stock_location.id,
  2328. 'location_dest_id': self.stock_location.id,
  2329. 'product_id': self.product.id,
  2330. 'product_uom': self.uom_unit.id,
  2331. 'product_uom_qty': 3.0,
  2332. })
  2333. move_stock_stock_1.write({'move_orig_ids': [(4, move_supp_stock_1.id, 0), (4, move_supp_stock_2.id, 0)]})
  2334. move_stock_stock_2 = self.env['stock.move'].create({
  2335. 'name': 'test_link_assign_6_1',
  2336. 'location_id': self.stock_location.id,
  2337. 'location_dest_id': self.stock_location.id,
  2338. 'product_id': self.product.id,
  2339. 'product_uom': self.uom_unit.id,
  2340. 'product_uom_qty': 3.0,
  2341. })
  2342. move_stock_stock_2.write({'move_orig_ids': [(4, move_supp_stock_1.id, 0), (4, move_supp_stock_2.id, 0)]})
  2343. (move_supp_stock_1 + move_supp_stock_2 + move_stock_stock_1 + move_stock_stock_2)._action_confirm()
  2344. move_supp_stock_1._action_assign()
  2345. self.assertEqual(move_supp_stock_1.state, 'assigned')
  2346. self.assertEqual(move_supp_stock_2.state, 'assigned')
  2347. self.assertEqual(move_stock_stock_1.state, 'waiting')
  2348. self.assertEqual(move_stock_stock_2.state, 'waiting')
  2349. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  2350. # do the fist move, it'll bring 3 units in stock location so only `move_stock_stock_1`
  2351. # should be assigned
  2352. move_supp_stock_1.move_line_ids.qty_done = 3.0
  2353. move_supp_stock_1._action_done()
  2354. self.assertEqual(move_supp_stock_1.state, 'done')
  2355. self.assertEqual(move_supp_stock_2.state, 'assigned')
  2356. self.assertEqual(move_stock_stock_1.state, 'assigned')
  2357. self.assertEqual(move_stock_stock_2.state, 'waiting')
  2358. def test_link_assign_7(self):
  2359. # on the dozen uom, set the rounding set 1.0
  2360. self.uom_dozen.rounding = 1
  2361. # 6 units are available in stock
  2362. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 6.0)
  2363. # create pickings and moves for a pick -> pack mto scenario
  2364. picking_stock_pack = self.env['stock.picking'].create({
  2365. 'location_id': self.stock_location.id,
  2366. 'location_dest_id': self.pack_location.id,
  2367. 'picking_type_id': self.env.ref('stock.picking_type_internal').id,
  2368. })
  2369. move_stock_pack = self.env['stock.move'].create({
  2370. 'name': 'test_link_assign_7',
  2371. 'location_id': self.stock_location.id,
  2372. 'location_dest_id': self.pack_location.id,
  2373. 'product_id': self.product.id,
  2374. 'product_uom': self.uom_dozen.id,
  2375. 'product_uom_qty': 1.0,
  2376. 'picking_id': picking_stock_pack.id,
  2377. })
  2378. picking_pack_cust = self.env['stock.picking'].create({
  2379. 'location_id': self.pack_location.id,
  2380. 'location_dest_id': self.customer_location.id,
  2381. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  2382. })
  2383. move_pack_cust = self.env['stock.move'].create({
  2384. 'name': 'test_link_assign_7',
  2385. 'location_id': self.pack_location.id,
  2386. 'location_dest_id': self.customer_location.id,
  2387. 'product_id': self.product.id,
  2388. 'product_uom': self.uom_dozen.id,
  2389. 'product_uom_qty': 1.0,
  2390. 'picking_id': picking_pack_cust.id,
  2391. })
  2392. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2393. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2394. (move_stock_pack + move_pack_cust)._action_confirm()
  2395. # the pick should not be reservable because of the rounding of the dozen
  2396. move_stock_pack._action_assign()
  2397. self.assertEqual(move_stock_pack.state, 'confirmed')
  2398. move_pack_cust._action_assign()
  2399. self.assertEqual(move_pack_cust.state, 'waiting')
  2400. # move the 6 units by adding an unreserved move line
  2401. move_stock_pack.write({'move_line_ids': [(0, 0, {
  2402. 'product_id': self.product.id,
  2403. 'product_uom_id': self.uom_unit.id,
  2404. 'qty_done': 6,
  2405. 'reserved_uom_qty': 0,
  2406. 'lot_id': False,
  2407. 'package_id': False,
  2408. 'result_package_id': False,
  2409. 'location_id': move_stock_pack.location_id.id,
  2410. 'location_dest_id': move_stock_pack.location_dest_id.id,
  2411. 'picking_id': picking_stock_pack.id,
  2412. })]})
  2413. # the quantity done on the move should not respect the rounding of the move line
  2414. self.assertEqual(move_stock_pack.quantity_done, 0.5)
  2415. # create the backorder in the uom of the quants
  2416. backorder_wizard_dict = picking_stock_pack.button_validate()
  2417. backorder_wizard = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context'])).save()
  2418. backorder_wizard.process()
  2419. self.assertEqual(move_stock_pack.state, 'done')
  2420. self.assertEqual(move_stock_pack.quantity_done, 0.5)
  2421. self.assertEqual(move_stock_pack.product_uom_qty, 0.5)
  2422. # the second move should not be reservable because of the rounding on the dozen
  2423. move_pack_cust._action_assign()
  2424. self.assertEqual(move_pack_cust.state, 'partially_available')
  2425. move_line_pack_cust = move_pack_cust.move_line_ids
  2426. self.assertEqual(move_line_pack_cust.reserved_uom_qty, 6)
  2427. self.assertEqual(move_line_pack_cust.product_uom_id.id, self.uom_unit.id)
  2428. # move a dozen on the backorder to see how we handle the extra move
  2429. backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_stock_pack.id)])
  2430. backorder.move_ids.write({'move_line_ids': [(0, 0, {
  2431. 'product_id': self.product.id,
  2432. 'product_uom_id': self.uom_dozen.id,
  2433. 'qty_done': 1,
  2434. 'reserved_uom_qty': 0,
  2435. 'lot_id': False,
  2436. 'package_id': False,
  2437. 'result_package_id': False,
  2438. 'location_id': backorder.location_id.id,
  2439. 'location_dest_id': backorder.location_dest_id.id,
  2440. 'picking_id': backorder.id,
  2441. })]})
  2442. backorder.button_validate()
  2443. backorder_move = backorder.move_ids
  2444. self.assertEqual(backorder_move.state, 'done')
  2445. self.assertEqual(backorder_move.quantity_done, 12.0)
  2446. self.assertEqual(backorder_move.product_uom_qty, 12.0)
  2447. self.assertEqual(backorder_move.product_uom, self.uom_unit)
  2448. # the second move should now be reservable
  2449. move_pack_cust._action_assign()
  2450. self.assertEqual(move_pack_cust.state, 'assigned')
  2451. self.assertEqual(move_line_pack_cust.reserved_uom_qty, 12)
  2452. self.assertEqual(move_line_pack_cust.product_uom_id.id, self.uom_unit.id)
  2453. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, move_stock_pack.location_dest_id), 6)
  2454. def test_link_assign_8(self):
  2455. """ Set the rounding of the dozen to 1.0, create a chain of two move for a dozen, the product
  2456. concerned is tracked by serial number. Check that the flow is ok.
  2457. """
  2458. # on the dozen uom, set the rounding set 1.0
  2459. self.uom_dozen.rounding = 1
  2460. # 6 units are available in stock
  2461. for i in range(1, 13):
  2462. lot_id = self.env['stock.lot'].create({
  2463. 'name': 'lot%s' % str(i),
  2464. 'product_id': self.product_serial.id,
  2465. 'company_id': self.env.company.id,
  2466. })
  2467. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1.0, lot_id=lot_id)
  2468. # create pickings and moves for a pick -> pack mto scenario
  2469. picking_stock_pack = self.env['stock.picking'].create({
  2470. 'location_id': self.stock_location.id,
  2471. 'location_dest_id': self.pack_location.id,
  2472. 'picking_type_id': self.env.ref('stock.picking_type_internal').id,
  2473. })
  2474. move_stock_pack = self.env['stock.move'].create({
  2475. 'name': 'test_link_assign_7',
  2476. 'location_id': self.stock_location.id,
  2477. 'location_dest_id': self.pack_location.id,
  2478. 'product_id': self.product_serial.id,
  2479. 'product_uom': self.uom_dozen.id,
  2480. 'product_uom_qty': 1.0,
  2481. 'picking_id': picking_stock_pack.id,
  2482. })
  2483. picking_pack_cust = self.env['stock.picking'].create({
  2484. 'location_id': self.pack_location.id,
  2485. 'location_dest_id': self.customer_location.id,
  2486. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  2487. })
  2488. move_pack_cust = self.env['stock.move'].create({
  2489. 'name': 'test_link_assign_7',
  2490. 'location_id': self.pack_location.id,
  2491. 'location_dest_id': self.customer_location.id,
  2492. 'product_id': self.product_serial.id,
  2493. 'product_uom': self.uom_dozen.id,
  2494. 'product_uom_qty': 1.0,
  2495. 'picking_id': picking_pack_cust.id,
  2496. })
  2497. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2498. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2499. (move_stock_pack + move_pack_cust)._action_confirm()
  2500. move_stock_pack._action_assign()
  2501. self.assertEqual(move_stock_pack.state, 'assigned')
  2502. move_pack_cust._action_assign()
  2503. self.assertEqual(move_pack_cust.state, 'waiting')
  2504. for ml in move_stock_pack.move_line_ids:
  2505. ml.qty_done = 1
  2506. picking_stock_pack.button_validate()
  2507. self.assertEqual(move_pack_cust.state, 'assigned')
  2508. for ml in move_pack_cust.move_line_ids:
  2509. self.assertEqual(ml.reserved_uom_qty, 1)
  2510. self.assertEqual(ml.product_uom_id.id, self.uom_unit.id)
  2511. self.assertTrue(bool(ml.lot_id.id))
  2512. def test_link_assign_9(self):
  2513. """ Create an uom "3 units" which is 3 times the units but without rounding. Create 3
  2514. quants in stock and two chained moves. The first move will bring the 3 quants but the
  2515. second only validate 2 and create a backorder for the last one. Check that the reservation
  2516. is correctly cleared up for the last one.
  2517. """
  2518. uom_3units = self.env['uom.uom'].create({
  2519. 'name': '3 units',
  2520. 'category_id': self.uom_unit.category_id.id,
  2521. 'factor_inv': 3,
  2522. 'rounding': 1,
  2523. 'uom_type': 'bigger',
  2524. })
  2525. for i in range(1, 4):
  2526. lot_id = self.env['stock.lot'].create({
  2527. 'name': 'lot%s' % str(i),
  2528. 'product_id': self.product_serial.id,
  2529. 'company_id': self.env.company.id,
  2530. })
  2531. self.env['stock.quant']._update_available_quantity(self.product_serial, self.stock_location, 1.0, lot_id=lot_id)
  2532. picking_stock_pack = self.env['stock.picking'].create({
  2533. 'location_id': self.stock_location.id,
  2534. 'location_dest_id': self.pack_location.id,
  2535. 'picking_type_id': self.env.ref('stock.picking_type_internal').id,
  2536. })
  2537. move_stock_pack = self.env['stock.move'].create({
  2538. 'name': 'test_link_assign_9',
  2539. 'location_id': self.stock_location.id,
  2540. 'location_dest_id': self.pack_location.id,
  2541. 'product_id': self.product_serial.id,
  2542. 'product_uom': uom_3units.id,
  2543. 'product_uom_qty': 1.0,
  2544. 'picking_id': picking_stock_pack.id,
  2545. })
  2546. picking_pack_cust = self.env['stock.picking'].create({
  2547. 'location_id': self.pack_location.id,
  2548. 'location_dest_id': self.customer_location.id,
  2549. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  2550. })
  2551. move_pack_cust = self.env['stock.move'].create({
  2552. 'name': 'test_link_assign_0',
  2553. 'location_id': self.pack_location.id,
  2554. 'location_dest_id': self.customer_location.id,
  2555. 'product_id': self.product_serial.id,
  2556. 'product_uom': uom_3units.id,
  2557. 'product_uom_qty': 1.0,
  2558. 'picking_id': picking_pack_cust.id,
  2559. })
  2560. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2561. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2562. (move_stock_pack + move_pack_cust)._action_confirm()
  2563. picking_stock_pack.action_assign()
  2564. for ml in picking_stock_pack.move_ids.move_line_ids:
  2565. ml.qty_done = 1
  2566. picking_stock_pack.button_validate()
  2567. self.assertEqual(picking_pack_cust.state, 'assigned')
  2568. for ml in picking_pack_cust.move_ids.move_line_ids:
  2569. if ml.lot_id.name != 'lot3':
  2570. ml.qty_done = 1
  2571. res_dict_for_back_order = picking_pack_cust.button_validate()
  2572. backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
  2573. backorder_wizard.process()
  2574. backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pack_cust.id)])
  2575. backordered_move = backorder.move_ids
  2576. # due to the rounding, the backordered quantity is 0.999 ; we shoudln't be able to reserve
  2577. # 0.999 on a tracked by serial number quant
  2578. backordered_move._action_assign()
  2579. self.assertEqual(backordered_move.reserved_availability, 0)
  2580. # force the serial number and validate
  2581. lot3 = self.env['stock.lot'].search([('name', '=', "lot3")])
  2582. backorder.write({'move_line_ids': [(0, 0, {
  2583. 'product_id': self.product_serial.id,
  2584. 'product_uom_id': self.uom_unit.id,
  2585. 'qty_done': 1,
  2586. 'reserved_uom_qty': 0,
  2587. 'lot_id': lot3.id,
  2588. 'package_id': False,
  2589. 'result_package_id': False,
  2590. 'location_id': backordered_move.location_id.id,
  2591. 'location_dest_id': backordered_move.location_dest_id.id,
  2592. 'move_id': backordered_move.id,
  2593. })]})
  2594. backorder.button_validate()
  2595. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.customer_location), 3)
  2596. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_serial, self.pack_location), 0)
  2597. def test_link_assign_10(self):
  2598. """ Test the assignment mechanism with partial availability.
  2599. """
  2600. # make some stock:
  2601. # stock location: 2.0
  2602. # pack location: -1.0
  2603. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2.0)
  2604. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 1.0)
  2605. move_out = self.env['stock.move'].create({
  2606. 'name': 'test_link_assign_out',
  2607. 'location_id': self.pack_location.id,
  2608. 'location_dest_id': self.customer_location.id,
  2609. 'product_id': self.product.id,
  2610. 'product_uom': self.uom_unit.id,
  2611. 'product_uom_qty': 1.0,
  2612. })
  2613. move_out._action_confirm()
  2614. move_out._action_assign()
  2615. move_out.quantity_done = 1.0
  2616. move_out._action_done()
  2617. self.assertEqual(len(self.gather_relevant(self.product, self.pack_location)), 1.0)
  2618. move_stock_pack = self.env['stock.move'].create({
  2619. 'name': 'test_link_assign_1_1',
  2620. 'location_id': self.stock_location.id,
  2621. 'location_dest_id': self.pack_location.id,
  2622. 'product_id': self.product.id,
  2623. 'product_uom': self.uom_unit.id,
  2624. 'product_uom_qty': 2.0,
  2625. })
  2626. move_pack_cust = self.env['stock.move'].create({
  2627. 'name': 'test_link_assign_1_2',
  2628. 'location_id': self.pack_location.id,
  2629. 'location_dest_id': self.customer_location.id,
  2630. 'product_id': self.product.id,
  2631. 'product_uom': self.uom_unit.id,
  2632. 'product_uom_qty': 2.0,
  2633. })
  2634. move_stock_pack.write({'move_dest_ids': [(4, move_pack_cust.id, 0)]})
  2635. move_pack_cust.write({'move_orig_ids': [(4, move_stock_pack.id, 0)]})
  2636. (move_stock_pack + move_pack_cust)._action_confirm()
  2637. move_stock_pack._action_assign()
  2638. move_stock_pack.quantity_done = 2.0
  2639. move_stock_pack._action_done()
  2640. self.assertEqual(len(move_pack_cust.move_line_ids), 1)
  2641. self.assertAlmostEqual(move_pack_cust.reserved_availability, 1.0)
  2642. self.assertEqual(move_pack_cust.state, 'partially_available')
  2643. def test_use_reserved_move_line_1(self):
  2644. """ Test that _free_reservation work when quantity is only available on
  2645. reserved move lines.
  2646. """
  2647. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 10.0)
  2648. move1 = self.env['stock.move'].create({
  2649. 'name': 'test_use_unreserved_move_line_1_1',
  2650. 'location_id': self.stock_location.id,
  2651. 'location_dest_id': self.customer_location.id,
  2652. 'product_id': self.product.id,
  2653. 'product_uom': self.uom_unit.id,
  2654. 'product_uom_qty': 5.0,
  2655. })
  2656. move2 = self.env['stock.move'].create({
  2657. 'name': 'test_use_unreserved_move_line_1_1',
  2658. 'location_id': self.stock_location.id,
  2659. 'location_dest_id': self.customer_location.id,
  2660. 'product_id': self.product.id,
  2661. 'product_uom': self.uom_unit.id,
  2662. 'product_uom_qty': 5.0,
  2663. })
  2664. move1._action_confirm()
  2665. move1._action_assign()
  2666. move2._action_confirm()
  2667. move2._action_assign()
  2668. move3 = self.env['stock.move'].create({
  2669. 'name': 'test_use_unreserved_move_line_1_1',
  2670. 'location_id': self.stock_location.id,
  2671. 'location_dest_id': self.customer_location.id,
  2672. 'product_id': self.product.id,
  2673. 'product_uom': self.uom_unit.id,
  2674. 'product_uom_qty': 0.0,
  2675. 'quantity_done': 1.0,
  2676. })
  2677. move3._action_confirm()
  2678. move3._action_assign()
  2679. move3._action_done()
  2680. self.assertEqual(move3.state, 'done')
  2681. quant = self.env['stock.quant']._gather(self.product, self.stock_location)
  2682. self.assertEqual(quant.quantity, 9.0)
  2683. self.assertEqual(quant.reserved_quantity, 9.0)
  2684. def test_use_reserved_move_line_2(self):
  2685. # make 12 units available in stock
  2686. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 12.0)
  2687. # reserve 12 units
  2688. move1 = self.env['stock.move'].create({
  2689. 'name': 'test_use_reserved_move_line_2_1',
  2690. 'location_id': self.stock_location.id,
  2691. 'location_dest_id': self.customer_location.id,
  2692. 'product_id': self.product.id,
  2693. 'product_uom': self.uom_unit.id,
  2694. 'product_uom_qty': 12,
  2695. })
  2696. move1._action_confirm()
  2697. move1._action_assign()
  2698. self.assertEqual(move1.state, 'assigned')
  2699. quant = self.env['stock.quant']._gather(self.product, self.stock_location)
  2700. self.assertEqual(quant.quantity, 12)
  2701. self.assertEqual(quant.reserved_quantity, 12)
  2702. # force a move of 1 dozen
  2703. move2 = self.env['stock.move'].create({
  2704. 'name': 'test_use_reserved_move_line_2_2',
  2705. 'location_id': self.stock_location.id,
  2706. 'location_dest_id': self.customer_location.id,
  2707. 'product_id': self.product.id,
  2708. 'product_uom': self.uom_dozen.id,
  2709. 'product_uom_qty': 1,
  2710. })
  2711. move2._action_confirm()
  2712. move2._action_assign()
  2713. self.assertEqual(move2.state, 'confirmed')
  2714. move2._set_quantity_done(1)
  2715. move2._action_done()
  2716. # mov1 should be unreserved and the quant should be unlinked
  2717. self.assertEqual(move1.state, 'confirmed')
  2718. quant = self.env['stock.quant']._gather(self.product, self.stock_location)
  2719. self.assertEqual(quant.quantity, 0)
  2720. self.assertEqual(quant.reserved_quantity, 0)
  2721. def test_use_unreserved_move_line_1(self):
  2722. """ Test that validating a stock move linked to an untracked product reserved by another one
  2723. correctly unreserves the other one.
  2724. """
  2725. # make some stock
  2726. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0)
  2727. # prepare the conflicting move
  2728. move1 = self.env['stock.move'].create({
  2729. 'name': 'test_use_unreserved_move_line_1_1',
  2730. 'location_id': self.stock_location.id,
  2731. 'location_dest_id': self.customer_location.id,
  2732. 'product_id': self.product.id,
  2733. 'product_uom': self.uom_unit.id,
  2734. 'product_uom_qty': 1.0,
  2735. })
  2736. move2 = self.env['stock.move'].create({
  2737. 'name': 'test_use_unreserved_move_line_1_1',
  2738. 'location_id': self.stock_location.id,
  2739. 'location_dest_id': self.customer_location.id,
  2740. 'product_id': self.product.id,
  2741. 'product_uom': self.uom_unit.id,
  2742. 'product_uom_qty': 1.0,
  2743. })
  2744. # reserve those move
  2745. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2746. move1._action_confirm()
  2747. move1._action_assign()
  2748. self.assertEqual(move1.state, 'assigned')
  2749. move2._action_confirm()
  2750. move2._action_assign()
  2751. self.assertEqual(move2.state, 'confirmed')
  2752. # use the product from the first one
  2753. move2.write({'move_line_ids': [(0, 0, {
  2754. 'product_id': self.product.id,
  2755. 'product_uom_id': self.uom_unit.id,
  2756. 'qty_done': 1,
  2757. 'reserved_uom_qty': 0,
  2758. 'lot_id': False,
  2759. 'package_id': False,
  2760. 'result_package_id': False,
  2761. 'location_id': move2.location_id.id,
  2762. 'location_dest_id': move2.location_dest_id.id,
  2763. })]})
  2764. move2._action_done()
  2765. # the first move should go back to confirmed
  2766. self.assertEqual(move1.state, 'confirmed')
  2767. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  2768. def test_use_unreserved_move_line_2(self):
  2769. """ Test that validating a stock move linked to a tracked product reserved by another one
  2770. correctly unreserves the other one.
  2771. """
  2772. lot1 = self.env['stock.lot'].create({
  2773. 'name': 'lot1',
  2774. 'product_id': self.product.id,
  2775. 'company_id': self.env.company.id,
  2776. })
  2777. # make some stock
  2778. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot1)
  2779. # prepare the conflicting move
  2780. move1 = self.env['stock.move'].create({
  2781. 'name': 'test_use_unreserved_move_line_1_1',
  2782. 'location_id': self.stock_location.id,
  2783. 'location_dest_id': self.customer_location.id,
  2784. 'product_id': self.product.id,
  2785. 'product_uom': self.uom_unit.id,
  2786. 'product_uom_qty': 1.0,
  2787. })
  2788. move2 = self.env['stock.move'].create({
  2789. 'name': 'test_use_unreserved_move_line_1_1',
  2790. 'location_id': self.stock_location.id,
  2791. 'location_dest_id': self.customer_location.id,
  2792. 'product_id': self.product.id,
  2793. 'product_uom': self.uom_unit.id,
  2794. 'product_uom_qty': 1.0,
  2795. })
  2796. # reserve those move
  2797. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 1.0)
  2798. move1._action_confirm()
  2799. move1._action_assign()
  2800. self.assertEqual(move1.state, 'assigned')
  2801. move2._action_confirm()
  2802. move2._action_assign()
  2803. self.assertEqual(move2.state, 'confirmed')
  2804. # use the product from the first one
  2805. move2.write({'move_line_ids': [(0, 0, {
  2806. 'product_id': self.product.id,
  2807. 'product_uom_id': self.uom_unit.id,
  2808. 'qty_done': 1,
  2809. 'reserved_uom_qty': 0,
  2810. 'lot_id': lot1.id,
  2811. 'package_id': False,
  2812. 'result_package_id': False,
  2813. 'location_id': move2.location_id.id,
  2814. 'location_dest_id': move2.location_dest_id.id,
  2815. })]})
  2816. move2._action_done()
  2817. # the first move should go back to confirmed
  2818. self.assertEqual(move1.state, 'confirmed')
  2819. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 0.0)
  2820. def test_use_unreserved_move_line_3(self):
  2821. """ Test the behavior of `_free_reservation` when ran on a recordset of move lines where
  2822. some are assigned and some are force assigned. `_free_reservation` should not use an
  2823. already processed move line when looking for a move line candidate to unreserve.
  2824. """
  2825. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0)
  2826. move1 = self.env['stock.move'].create({
  2827. 'name': 'test_use_unreserved_move_line_3',
  2828. 'location_id': self.stock_location.id,
  2829. 'location_dest_id': self.customer_location.id,
  2830. 'product_id': self.product.id,
  2831. 'product_uom': self.uom_unit.id,
  2832. 'product_uom_qty': 3.0,
  2833. })
  2834. move1._action_confirm()
  2835. move1._action_assign()
  2836. move1.quantity_done = 1
  2837. # add a forced move line in `move1`
  2838. move1.write({'move_line_ids': [(0, 0, {
  2839. 'product_id': self.product.id,
  2840. 'product_uom_id': self.uom_unit.id,
  2841. 'qty_done': 2,
  2842. 'reserved_uom_qty': 0,
  2843. 'lot_id': False,
  2844. 'package_id': False,
  2845. 'result_package_id': False,
  2846. 'location_id': move1.location_id.id,
  2847. 'location_dest_id': move1.location_dest_id.id,
  2848. })]})
  2849. move1._action_done()
  2850. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 3.0)
  2851. def test_use_unreserved_move_line_4(self):
  2852. product_01 = self.env['product.product'].create({
  2853. 'name': 'Product 01',
  2854. 'type': 'product',
  2855. 'categ_id': self.env.ref('product.product_category_all').id,
  2856. })
  2857. product_02 = self.env['product.product'].create({
  2858. 'name': 'Product 02',
  2859. 'type': 'product',
  2860. 'categ_id': self.env.ref('product.product_category_all').id,
  2861. })
  2862. self.env['stock.quant']._update_available_quantity(product_01, self.stock_location, 1)
  2863. self.env['stock.quant']._update_available_quantity(product_02, self.stock_location, 1)
  2864. customer = self.env['res.partner'].create({'name': 'SuperPartner'})
  2865. picking = self.env['stock.picking'].create({
  2866. 'location_id': self.stock_location.id,
  2867. 'location_dest_id': self.customer_location.id,
  2868. 'partner_id': customer.id,
  2869. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  2870. })
  2871. p01_move = self.env['stock.move'].create({
  2872. 'name': 'SuperMove01',
  2873. 'location_id': picking.location_id.id,
  2874. 'location_dest_id': picking.location_dest_id.id,
  2875. 'picking_id': picking.id,
  2876. 'product_id': product_01.id,
  2877. 'product_uom_qty': 1,
  2878. 'product_uom': product_01.uom_id.id,
  2879. })
  2880. self.env['stock.move'].create({
  2881. 'name': 'SuperMove02',
  2882. 'location_id': picking.location_id.id,
  2883. 'location_dest_id': picking.location_dest_id.id,
  2884. 'picking_id': picking.id,
  2885. 'product_id': product_02.id,
  2886. 'product_uom_qty': 1,
  2887. 'product_uom': product_02.uom_id.id,
  2888. })
  2889. picking.action_confirm()
  2890. picking.action_assign()
  2891. p01_move.product_uom_qty = 0
  2892. picking.do_unreserve()
  2893. picking.action_assign()
  2894. p01_move.product_uom_qty = 1
  2895. self.assertEqual(p01_move.state, 'confirmed')
  2896. def test_edit_reserved_move_line_1(self):
  2897. """ Test that editing a stock move line linked to an untracked product correctly and
  2898. directly adapts the reservation. In this case, we edit the sublocation where we take the
  2899. product to another sublocation where a product is available.
  2900. """
  2901. shelf1_location = self.env['stock.location'].create({
  2902. 'name': 'shelf1',
  2903. 'usage': 'internal',
  2904. 'location_id': self.stock_location.id,
  2905. })
  2906. shelf2_location = self.env['stock.location'].create({
  2907. 'name': 'shelf1',
  2908. 'usage': 'internal',
  2909. 'location_id': self.stock_location.id,
  2910. })
  2911. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  2912. self.env['stock.quant']._update_available_quantity(self.product, shelf2_location, 1.0)
  2913. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  2914. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 1.0)
  2915. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  2916. move1 = self.env['stock.move'].create({
  2917. 'name': 'test_edit_moveline_1',
  2918. 'location_id': self.stock_location.id,
  2919. 'location_dest_id': self.customer_location.id,
  2920. 'product_id': self.product.id,
  2921. 'product_uom': self.uom_unit.id,
  2922. 'product_uom_qty': 1.0,
  2923. })
  2924. move1._action_confirm()
  2925. move1._action_assign()
  2926. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  2927. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 1.0)
  2928. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2929. move1.move_line_ids.location_id = shelf2_location.id
  2930. self.assertEqual(move1.reserved_availability, 1.0)
  2931. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  2932. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  2933. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2934. def test_edit_reserved_move_line_2(self):
  2935. """ Test that editing a stock move line linked to a tracked product correctly and directly
  2936. adapts the reservation. In this case, we edit the lot to another available one.
  2937. """
  2938. lot1 = self.env['stock.lot'].create({
  2939. 'name': 'lot1',
  2940. 'product_id': self.product.id,
  2941. 'company_id': self.env.company.id,
  2942. })
  2943. lot2 = self.env['stock.lot'].create({
  2944. 'name': 'lot2',
  2945. 'product_id': self.product.id,
  2946. 'company_id': self.env.company.id,
  2947. })
  2948. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot1)
  2949. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot2)
  2950. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  2951. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 1.0)
  2952. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  2953. move1 = self.env['stock.move'].create({
  2954. 'name': 'test_edit_moveline_1',
  2955. 'location_id': self.stock_location.id,
  2956. 'location_dest_id': self.customer_location.id,
  2957. 'product_id': self.product.id,
  2958. 'product_uom': self.uom_unit.id,
  2959. 'product_uom_qty': 1.0,
  2960. })
  2961. move1._action_confirm()
  2962. move1._action_assign()
  2963. self.assertEqual(move1.reserved_availability, 1.0)
  2964. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2965. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 0.0)
  2966. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  2967. move1.move_line_ids.lot_id = lot2.id
  2968. self.assertEqual(move1.reserved_availability, 1.0)
  2969. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2970. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 1.0)
  2971. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 0.0)
  2972. def test_edit_reserved_move_line_3(self):
  2973. """ Test that editing a stock move line linked to a packed product correctly and directly
  2974. adapts the reservation. In this case, we edit the package to another available one.
  2975. """
  2976. package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
  2977. package2 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
  2978. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, package_id=package1)
  2979. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, package_id=package2)
  2980. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  2981. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 1.0)
  2982. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package2), 1.0)
  2983. move1 = self.env['stock.move'].create({
  2984. 'name': 'test_edit_moveline_1',
  2985. 'location_id': self.stock_location.id,
  2986. 'location_dest_id': self.customer_location.id,
  2987. 'product_id': self.product.id,
  2988. 'product_uom': self.uom_unit.id,
  2989. 'product_uom_qty': 1.0,
  2990. })
  2991. move1._action_confirm()
  2992. move1._action_assign()
  2993. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2994. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 0.0)
  2995. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package2), 1.0)
  2996. move1.move_line_ids.package_id = package2.id
  2997. self.assertEqual(move1.reserved_availability, 1.0)
  2998. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  2999. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 1.0)
  3000. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package2), 0.0)
  3001. def test_edit_reserved_move_line_4(self):
  3002. """ Test that editing a stock move line linked to an owned product correctly and directly
  3003. adapts the reservation. In this case, we edit the owner to another available one.
  3004. """
  3005. owner1 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_1'})
  3006. owner2 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_2'})
  3007. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, owner_id=owner1)
  3008. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, owner_id=owner2)
  3009. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3010. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner1), 1.0)
  3011. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner2), 1.0)
  3012. move1 = self.env['stock.move'].create({
  3013. 'name': 'test_edit_moveline_1',
  3014. 'location_id': self.stock_location.id,
  3015. 'location_dest_id': self.customer_location.id,
  3016. 'product_id': self.product.id,
  3017. 'product_uom': self.uom_unit.id,
  3018. 'product_uom_qty': 1.0,
  3019. })
  3020. move1._action_confirm()
  3021. move1._action_assign()
  3022. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3023. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner1), 0.0)
  3024. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner2), 1.0)
  3025. move1.move_line_ids.owner_id = owner2.id
  3026. self.assertEqual(move1.reserved_availability, 1.0)
  3027. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3028. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner1), 1.0)
  3029. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner2), 0.0)
  3030. def test_edit_reserved_move_line_5(self):
  3031. """ Test that editing a stock move line linked to a packed and tracked product correctly
  3032. and directly adapts the reservation. In this case, we edit the lot to another available one
  3033. that is not in a pack.
  3034. """
  3035. lot1 = self.env['stock.lot'].create({
  3036. 'name': 'lot1',
  3037. 'product_id': self.product.id,
  3038. 'company_id': self.env.company.id,
  3039. })
  3040. lot2 = self.env['stock.lot'].create({
  3041. 'name': 'lot2',
  3042. 'product_id': self.product.id,
  3043. 'company_id': self.env.company.id,
  3044. })
  3045. package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_5'})
  3046. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot1, package_id=package1)
  3047. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot2)
  3048. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3049. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
  3050. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  3051. move1 = self.env['stock.move'].create({
  3052. 'name': 'test_edit_moveline_1',
  3053. 'location_id': self.stock_location.id,
  3054. 'location_dest_id': self.customer_location.id,
  3055. 'product_id': self.product.id,
  3056. 'product_uom': self.uom_unit.id,
  3057. 'product_uom_qty': 1.0,
  3058. })
  3059. move1._action_confirm()
  3060. move1._action_assign()
  3061. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3062. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1, package_id=package1), 0.0)
  3063. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  3064. move_line = move1.move_line_ids[0]
  3065. move_line.write({'package_id': False, 'lot_id': lot2.id})
  3066. self.assertEqual(move1.reserved_availability, 1.0)
  3067. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3068. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
  3069. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 0.0)
  3070. def test_edit_reserved_move_line_6(self):
  3071. """ Test that editing a stock move line linked to an untracked product correctly and
  3072. directly adapts the reservation. In this case, we edit the sublocation where we take the
  3073. product to another sublocation where a product is NOT available.
  3074. """
  3075. shelf1_location = self.env['stock.location'].create({
  3076. 'name': 'shelf1',
  3077. 'usage': 'internal',
  3078. 'location_id': self.stock_location.id,
  3079. })
  3080. shelf2_location = self.env['stock.location'].create({
  3081. 'name': 'shelf1',
  3082. 'usage': 'internal',
  3083. 'location_id': self.stock_location.id,
  3084. })
  3085. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  3086. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3087. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3088. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3089. move1 = self.env['stock.move'].create({
  3090. 'name': 'test_edit_moveline_1',
  3091. 'location_id': self.stock_location.id,
  3092. 'location_dest_id': self.customer_location.id,
  3093. 'product_id': self.product.id,
  3094. 'product_uom': self.uom_unit.id,
  3095. 'product_uom_qty': 1.0,
  3096. })
  3097. move1._action_confirm()
  3098. move1._action_assign()
  3099. self.assertEqual(move1.move_line_ids.state, 'assigned')
  3100. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3101. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3102. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3103. move1.move_line_ids.location_id = shelf2_location.id
  3104. self.assertEqual(move1.move_line_ids.state, 'confirmed')
  3105. self.assertEqual(move1.reserved_availability, 0.0)
  3106. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3107. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3108. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3109. def test_edit_reserved_move_line_7(self):
  3110. """ Send 5 tracked products to a client, but these products do not have any lot set in our
  3111. inventory yet: we only set them at delivery time. The created move line should have 5 items
  3112. without any lot set, if we edit to set them to lot1, the reservation should not change.
  3113. Validating the stock move should should not create a negative quant for this lot in stock
  3114. location.
  3115. # """
  3116. lot1 = self.env['stock.lot'].create({
  3117. 'name': 'lot1',
  3118. 'product_id': self.product_lot.id,
  3119. 'company_id': self.env.company.id,
  3120. })
  3121. # make some stock without assigning a lot id
  3122. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 5)
  3123. # creation
  3124. move1 = self.env['stock.move'].create({
  3125. 'name': 'test_in_1',
  3126. 'location_id': self.stock_location.id,
  3127. 'location_dest_id': self.customer_location.id,
  3128. 'product_id': self.product_lot.id,
  3129. 'product_uom': self.uom_unit.id,
  3130. 'product_uom_qty': 5.0,
  3131. })
  3132. self.assertEqual(move1.state, 'draft')
  3133. # confirmation
  3134. move1._action_confirm()
  3135. self.assertEqual(move1.state, 'confirmed')
  3136. # assignment
  3137. move1._action_assign()
  3138. self.assertEqual(move1.state, 'assigned')
  3139. self.assertEqual(len(move1.move_line_ids), 1)
  3140. move_line = move1.move_line_ids[0]
  3141. self.assertEqual(move_line.reserved_qty, 5)
  3142. move_line.qty_done = 5.0
  3143. self.assertEqual(move_line.reserved_qty, 5) # don't change reservation
  3144. move_line.lot_id = lot1
  3145. self.assertEqual(move_line.reserved_qty, 5) # don't change reservation when assgning a lot now
  3146. move1._action_done()
  3147. self.assertEqual(move_line.reserved_qty, 0) # change reservation to 0 for done move
  3148. self.assertEqual(move1.state, 'done')
  3149. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location), 0.0)
  3150. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1, strict=True), 0.0)
  3151. self.assertEqual(len(self.gather_relevant(self.product_lot, self.stock_location)), 0.0)
  3152. self.assertEqual(len(self.gather_relevant(self.product_lot, self.stock_location, lot_id=lot1, strict=True)), 0.0)
  3153. def test_edit_reserved_move_line_8(self):
  3154. """ Send 5 tracked products to a client, but some of these products do not have any lot set
  3155. in our inventory yet: we only set them at delivery time. Adding a lot_id on the move line
  3156. that does not have any should not change its reservation, and validating should not create
  3157. a negative quant for this lot in stock.
  3158. """
  3159. lot1 = self.env['stock.lot'].create({
  3160. 'name': 'lot1',
  3161. 'product_id': self.product_lot.id,
  3162. 'company_id': self.env.company.id,
  3163. })
  3164. lot2 = self.env['stock.lot'].create({
  3165. 'name': 'lot2',
  3166. 'product_id': self.product_lot.id,
  3167. 'company_id': self.env.company.id,
  3168. })
  3169. # make some stock without assigning a lot id
  3170. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 3)
  3171. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 2, lot_id=lot1)
  3172. # creation
  3173. move1 = self.env['stock.move'].create({
  3174. 'name': 'test_in_1',
  3175. 'location_id': self.stock_location.id,
  3176. 'location_dest_id': self.customer_location.id,
  3177. 'product_id': self.product_lot.id,
  3178. 'product_uom': self.uom_unit.id,
  3179. 'product_uom_qty': 5.0,
  3180. })
  3181. self.assertEqual(move1.state, 'draft')
  3182. # confirmation
  3183. move1._action_confirm()
  3184. self.assertEqual(move1.state, 'confirmed')
  3185. # assignment
  3186. move1._action_assign()
  3187. self.assertEqual(move1.state, 'assigned')
  3188. self.assertEqual(len(move1.move_line_ids), 2)
  3189. tracked_move_line = None
  3190. untracked_move_line = None
  3191. for move_line in move1.move_line_ids:
  3192. if move_line.lot_id:
  3193. tracked_move_line = move_line
  3194. else:
  3195. untracked_move_line = move_line
  3196. self.assertEqual(tracked_move_line.reserved_qty, 2)
  3197. tracked_move_line.qty_done = 2
  3198. self.assertEqual(untracked_move_line.reserved_qty, 3)
  3199. untracked_move_line.lot_id = lot2
  3200. self.assertEqual(untracked_move_line.reserved_qty, 3) # don't change reservation
  3201. untracked_move_line.qty_done = 3
  3202. self.assertEqual(untracked_move_line.reserved_qty, 3) # don't change reservation
  3203. move1._action_done()
  3204. self.assertEqual(untracked_move_line.reserved_qty, 0) # change reservation to 0 for done move
  3205. self.assertEqual(tracked_move_line.reserved_qty, 0) # change reservation to 0 for done move
  3206. self.assertEqual(move1.state, 'done')
  3207. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location), 0.0)
  3208. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1, strict=True), 0.0)
  3209. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot2, strict=True), 0.0)
  3210. self.assertEqual(len(self.gather_relevant(self.product_lot, self.stock_location)), 0.0)
  3211. self.assertEqual(len(self.gather_relevant(self.product_lot, self.stock_location, lot_id=lot1, strict=True)), 0.0)
  3212. self.assertEqual(len(self.gather_relevant(self.product_lot, self.stock_location, lot_id=lot2, strict=True)), 0.0)
  3213. def test_edit_reserved_move_line_9(self):
  3214. """
  3215. When writing on the reserved quantity on the SML, a process tries to
  3216. reserve the quants with that new quantity. If it fails (for instance
  3217. because the written quantity is more than actually available), this
  3218. quantity should be reset to 0.
  3219. """
  3220. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0)
  3221. out_move = self.env['stock.move'].create({
  3222. 'name': self.product.name,
  3223. 'location_id': self.stock_location.id,
  3224. 'location_dest_id': self.customer_location.id,
  3225. 'product_id': self.product.id,
  3226. 'product_uom_qty': 1,
  3227. 'product_uom': self.product.uom_id.id,
  3228. })
  3229. out_move._action_confirm()
  3230. out_move._action_assign()
  3231. # try to manually assign more than available
  3232. out_move.move_line_ids.reserved_uom_qty = 2
  3233. self.assertTrue(out_move.move_line_ids)
  3234. self.assertEqual(out_move.move_line_ids.reserved_uom_qty, 0, "The reserved quantity should be cancelled")
  3235. def test_edit_done_move_line_1(self):
  3236. """ Test that editing a done stock move line linked to an untracked product correctly and
  3237. directly adapts the transfer. In this case, we edit the sublocation where we take the
  3238. product to another sublocation where a product is available.
  3239. """
  3240. shelf1_location = self.env['stock.location'].create({
  3241. 'name': 'shelf1',
  3242. 'usage': 'internal',
  3243. 'location_id': self.stock_location.id,
  3244. })
  3245. shelf2_location = self.env['stock.location'].create({
  3246. 'name': 'shelf1',
  3247. 'usage': 'internal',
  3248. 'location_id': self.stock_location.id,
  3249. })
  3250. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  3251. self.env['stock.quant']._update_available_quantity(self.product, shelf2_location, 1.0)
  3252. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3253. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 1.0)
  3254. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3255. # move from shelf1
  3256. move1 = self.env['stock.move'].create({
  3257. 'name': 'test_edit_moveline_1',
  3258. 'location_id': self.stock_location.id,
  3259. 'location_dest_id': self.customer_location.id,
  3260. 'product_id': self.product.id,
  3261. 'product_uom': self.uom_unit.id,
  3262. 'product_uom_qty': 1.0,
  3263. })
  3264. move1._action_confirm()
  3265. move1._action_assign()
  3266. move1.move_line_ids.qty_done = 1
  3267. move1._action_done()
  3268. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3269. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 1.0)
  3270. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3271. # edit once done, we actually moved from shelf2
  3272. move1.move_line_ids.location_id = shelf2_location.id
  3273. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3274. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3275. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3276. def test_edit_done_move_line_2(self):
  3277. """ Test that editing a done stock move line linked to a tracked product correctly and directly
  3278. adapts the transfer. In this case, we edit the lot to another available one.
  3279. """
  3280. lot1 = self.env['stock.lot'].create({
  3281. 'name': 'lot1',
  3282. 'product_id': self.product.id,
  3283. 'company_id': self.env.company.id,
  3284. })
  3285. lot2 = self.env['stock.lot'].create({
  3286. 'name': 'lot2',
  3287. 'product_id': self.product.id,
  3288. 'company_id': self.env.company.id,
  3289. })
  3290. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot1)
  3291. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot2)
  3292. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3293. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 1.0)
  3294. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  3295. move1 = self.env['stock.move'].create({
  3296. 'name': 'test_edit_moveline_1',
  3297. 'location_id': self.stock_location.id,
  3298. 'location_dest_id': self.customer_location.id,
  3299. 'product_id': self.product.id,
  3300. 'product_uom': self.uom_unit.id,
  3301. 'product_uom_qty': 1.0,
  3302. })
  3303. move1._action_confirm()
  3304. move1._action_assign()
  3305. move1.move_line_ids.qty_done = 1
  3306. move1._action_done()
  3307. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3308. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 0.0)
  3309. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  3310. move1.move_line_ids.lot_id = lot2.id
  3311. # reserved_availability should always been 0 for done move.
  3312. self.assertEqual(move1.reserved_availability, 0.0)
  3313. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3314. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1), 1.0)
  3315. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 0.0)
  3316. def test_edit_done_move_line_3(self):
  3317. """ Test that editing a done stock move line linked to a packed product correctly and directly
  3318. adapts the transfer. In this case, we edit the package to another available one.
  3319. """
  3320. package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
  3321. package2 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
  3322. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, package_id=package1)
  3323. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, package_id=package2)
  3324. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3325. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 1.0)
  3326. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package2), 1.0)
  3327. move1 = self.env['stock.move'].create({
  3328. 'name': 'test_edit_moveline_1',
  3329. 'location_id': self.stock_location.id,
  3330. 'location_dest_id': self.customer_location.id,
  3331. 'product_id': self.product.id,
  3332. 'product_uom': self.uom_unit.id,
  3333. 'product_uom_qty': 1.0,
  3334. })
  3335. move1._action_confirm()
  3336. move1._action_assign()
  3337. move1.move_line_ids.qty_done = 1
  3338. move1._action_done()
  3339. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3340. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 0.0)
  3341. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package2), 1.0)
  3342. move1.move_line_ids.package_id = package2.id
  3343. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3344. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package1), 1.0)
  3345. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, package_id=package2), 0.0)
  3346. def test_edit_done_move_line_4(self):
  3347. """ Test that editing a done stock move line linked to an owned product correctly and directly
  3348. adapts the transfer. In this case, we edit the owner to another available one.
  3349. """
  3350. owner1 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_1'})
  3351. owner2 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_2'})
  3352. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, owner_id=owner1)
  3353. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, owner_id=owner2)
  3354. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3355. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner1), 1.0)
  3356. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner2), 1.0)
  3357. move1 = self.env['stock.move'].create({
  3358. 'name': 'test_edit_moveline_1',
  3359. 'location_id': self.stock_location.id,
  3360. 'location_dest_id': self.customer_location.id,
  3361. 'product_id': self.product.id,
  3362. 'product_uom': self.uom_unit.id,
  3363. 'product_uom_qty': 1.0,
  3364. })
  3365. move1._action_confirm()
  3366. move1._action_assign()
  3367. move1.move_line_ids.qty_done = 1
  3368. move1._action_done()
  3369. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3370. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner1), 0.0)
  3371. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner2), 1.0)
  3372. move1.move_line_ids.owner_id = owner2.id
  3373. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3374. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner1), 1.0)
  3375. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, owner_id=owner2), 0.0)
  3376. def test_edit_done_move_line_5(self):
  3377. """ Test that editing a done stock move line linked to a packed and tracked product correctly
  3378. and directly adapts the transfer. In this case, we edit the lot to another available one
  3379. that is not in a pack.
  3380. """
  3381. lot1 = self.env['stock.lot'].create({
  3382. 'name': 'lot1',
  3383. 'product_id': self.product.id,
  3384. 'company_id': self.env.company.id,
  3385. })
  3386. lot2 = self.env['stock.lot'].create({
  3387. 'name': 'lot2',
  3388. 'product_id': self.product.id,
  3389. 'company_id': self.env.company.id,
  3390. })
  3391. package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_5'})
  3392. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot1, package_id=package1)
  3393. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1.0, lot_id=lot2)
  3394. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3395. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
  3396. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  3397. move1 = self.env['stock.move'].create({
  3398. 'name': 'test_edit_moveline_1',
  3399. 'location_id': self.stock_location.id,
  3400. 'location_dest_id': self.customer_location.id,
  3401. 'product_id': self.product.id,
  3402. 'product_uom': self.uom_unit.id,
  3403. 'product_uom_qty': 1.0,
  3404. })
  3405. move1._action_confirm()
  3406. move1._action_assign()
  3407. move1.move_line_ids.qty_done = 1
  3408. move1._action_done()
  3409. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3410. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1, package_id=package1), 0.0)
  3411. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 1.0)
  3412. move_line = move1.move_line_ids[0]
  3413. move_line.write({'package_id': False, 'lot_id': lot2.id})
  3414. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3415. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
  3416. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, lot_id=lot2), 0.0)
  3417. def test_edit_done_move_line_6(self):
  3418. """ Test that editing a done stock move line linked to an untracked product correctly and
  3419. directly adapts the transfer. In this case, we edit the sublocation where we take the
  3420. product to another sublocation where a product is NOT available.
  3421. """
  3422. shelf1_location = self.env['stock.location'].create({
  3423. 'name': 'shelf1',
  3424. 'usage': 'internal',
  3425. 'location_id': self.stock_location.id,
  3426. })
  3427. shelf2_location = self.env['stock.location'].create({
  3428. 'name': 'shelf1',
  3429. 'usage': 'internal',
  3430. 'location_id': self.stock_location.id,
  3431. })
  3432. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  3433. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3434. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3435. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3436. move1 = self.env['stock.move'].create({
  3437. 'name': 'test_edit_moveline_1',
  3438. 'location_id': self.stock_location.id,
  3439. 'location_dest_id': self.customer_location.id,
  3440. 'product_id': self.product.id,
  3441. 'product_uom': self.uom_unit.id,
  3442. 'product_uom_qty': 1.0,
  3443. })
  3444. move1._action_confirm()
  3445. move1._action_assign()
  3446. move1.move_line_ids.qty_done = 1
  3447. move1._action_done()
  3448. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3449. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3450. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3451. move1.move_line_ids.location_id = shelf2_location.id
  3452. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3453. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3454. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3455. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location, allow_negative=True), -1.0)
  3456. def test_edit_done_move_line_7(self):
  3457. """ Test that editing a done stock move line linked to an untracked product correctly and
  3458. directly adapts the transfer. In this case, we edit the sublocation where we take the
  3459. product to another sublocation where a product is NOT available because it has been reserved
  3460. by another move.
  3461. """
  3462. shelf1_location = self.env['stock.location'].create({
  3463. 'name': 'shelf1',
  3464. 'usage': 'internal',
  3465. 'location_id': self.stock_location.id,
  3466. })
  3467. shelf2_location = self.env['stock.location'].create({
  3468. 'name': 'shelf1',
  3469. 'usage': 'internal',
  3470. 'location_id': self.stock_location.id,
  3471. })
  3472. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  3473. self.env['stock.quant']._update_available_quantity(self.product, shelf2_location, 1.0)
  3474. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3475. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3476. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 1.0)
  3477. move1 = self.env['stock.move'].create({
  3478. 'name': 'test_edit_moveline_1',
  3479. 'location_id': self.stock_location.id,
  3480. 'location_dest_id': self.customer_location.id,
  3481. 'product_id': self.product.id,
  3482. 'product_uom': self.uom_unit.id,
  3483. 'product_uom_qty': 1.0,
  3484. })
  3485. move1._action_confirm()
  3486. move1._action_assign()
  3487. move1.move_line_ids.qty_done = 1
  3488. move1._action_done()
  3489. move2 = self.env['stock.move'].create({
  3490. 'name': 'test_edit_moveline_1',
  3491. 'location_id': self.stock_location.id,
  3492. 'location_dest_id': self.customer_location.id,
  3493. 'product_id': self.product.id,
  3494. 'product_uom': self.uom_unit.id,
  3495. 'product_uom_qty': 1.0,
  3496. })
  3497. move2._action_confirm()
  3498. move2._action_assign()
  3499. self.assertEqual(move2.state, 'assigned')
  3500. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3501. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3502. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3503. move1.move_line_ids.location_id = shelf2_location.id
  3504. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3505. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3506. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf2_location), 0.0)
  3507. self.assertEqual(move2.state, 'confirmed')
  3508. def test_edit_done_move_line_8(self):
  3509. """ Test that editing a done stock move line linked to an untracked product correctly and
  3510. directly adapts the transfer. In this case, we increment the quantity done (and we do not
  3511. have more in stock.
  3512. """
  3513. shelf1_location = self.env['stock.location'].create({
  3514. 'name': 'shelf1',
  3515. 'usage': 'internal',
  3516. 'location_id': self.stock_location.id,
  3517. })
  3518. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  3519. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3520. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3521. # move from shelf1
  3522. move1 = self.env['stock.move'].create({
  3523. 'name': 'test_edit_moveline_1',
  3524. 'location_id': self.stock_location.id,
  3525. 'location_dest_id': self.customer_location.id,
  3526. 'product_id': self.product.id,
  3527. 'product_uom': self.uom_unit.id,
  3528. 'product_uom_qty': 1.0,
  3529. })
  3530. move1._action_confirm()
  3531. move1._action_assign()
  3532. move1.move_line_ids.qty_done = 1
  3533. move1._action_done()
  3534. self.assertEqual(move1.product_uom_qty, 1.0)
  3535. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3536. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3537. # edit once done, we actually moved 2 products
  3538. move1.move_line_ids.qty_done = 2
  3539. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3540. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location, allow_negative=True), -1.0)
  3541. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3542. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location, allow_negative=True), -1.0)
  3543. self.assertEqual(move1.product_uom_qty, 2.0)
  3544. def test_edit_done_move_line_9(self):
  3545. """ Test that editing a done stock move line linked to an untracked product correctly and
  3546. directly adapts the transfer. In this case, we "cancel" the move by zeroing the qty done.
  3547. """
  3548. shelf1_location = self.env['stock.location'].create({
  3549. 'name': 'shelf1',
  3550. 'usage': 'internal',
  3551. 'location_id': self.stock_location.id,
  3552. })
  3553. self.env['stock.quant']._update_available_quantity(self.product, shelf1_location, 1.0)
  3554. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3555. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3556. # move from shelf1
  3557. move1 = self.env['stock.move'].create({
  3558. 'name': 'test_edit_moveline_1',
  3559. 'location_id': self.stock_location.id,
  3560. 'location_dest_id': self.customer_location.id,
  3561. 'product_id': self.product.id,
  3562. 'product_uom': self.uom_unit.id,
  3563. 'product_uom_qty': 1.0,
  3564. })
  3565. move1._action_confirm()
  3566. move1._action_assign()
  3567. move1.move_line_ids.qty_done = 1
  3568. move1._action_done()
  3569. self.assertEqual(move1.product_uom_qty, 1.0)
  3570. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 0.0)
  3571. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3572. # edit once done, we actually moved 2 products
  3573. move1.move_line_ids.qty_done = 0
  3574. self.assertEqual(move1.product_uom_qty, 0.0)
  3575. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, shelf1_location), 1.0)
  3576. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 1.0)
  3577. def test_edit_done_move_line_10(self):
  3578. """ Edit the quantity done for an incoming move shoudld also remove the quant if there
  3579. are no product in stock.
  3580. """
  3581. # move from shelf1
  3582. move1 = self.env['stock.move'].create({
  3583. 'name': 'test_edit_moveline_1',
  3584. 'location_id': self.supplier_location.id,
  3585. 'location_dest_id': self.stock_location.id,
  3586. 'product_id': self.product.id,
  3587. 'product_uom': self.uom_unit.id,
  3588. 'product_uom_qty': 10.0,
  3589. })
  3590. move1._action_confirm()
  3591. move1._action_assign()
  3592. move1.move_line_ids.qty_done = 10
  3593. move1._action_done()
  3594. quant = self.gather_relevant(self.product, self.stock_location)
  3595. self.assertEqual(len(quant), 1.0)
  3596. # edit once done, we actually moved 2 products
  3597. move1.move_line_ids.qty_done = 0
  3598. quant = self.gather_relevant(self.product, self.stock_location)
  3599. self.assertEqual(len(quant), 0.0)
  3600. self.assertEqual(move1.product_uom_qty, 0.0)
  3601. def test_edit_done_move_line_11(self):
  3602. """ Add a move line and check if the quant is updated
  3603. """
  3604. owner = self.env['res.partner'].create({'name': 'Jean'})
  3605. picking = self.env['stock.picking'].create({
  3606. 'location_id': self.supplier_location.id,
  3607. 'location_dest_id': self.stock_location.id,
  3608. 'partner_id': owner.id,
  3609. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  3610. })
  3611. # move from shelf1
  3612. move1 = self.env['stock.move'].create({
  3613. 'name': 'test_edit_moveline_1',
  3614. 'location_id': self.supplier_location.id,
  3615. 'location_dest_id': self.stock_location.id,
  3616. 'picking_id': picking.id,
  3617. 'product_id': self.product.id,
  3618. 'product_uom': self.uom_unit.id,
  3619. 'product_uom_qty': 10.0,
  3620. })
  3621. picking.action_confirm()
  3622. picking.action_assign()
  3623. move1.move_line_ids.qty_done = 10
  3624. picking._action_done()
  3625. self.assertEqual(move1.product_uom_qty, 10.0)
  3626. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 10.0)
  3627. self.env['stock.move.line'].create({
  3628. 'picking_id': move1.move_line_ids.picking_id.id,
  3629. 'move_id': move1.move_line_ids.move_id.id,
  3630. 'product_id': move1.move_line_ids.product_id.id,
  3631. 'qty_done': move1.move_line_ids.qty_done,
  3632. 'product_uom_id': move1.product_uom.id,
  3633. 'location_id': move1.move_line_ids.location_id.id,
  3634. 'location_dest_id': move1.move_line_ids.location_dest_id.id,
  3635. })
  3636. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 20.0)
  3637. move1.move_line_ids[1].qty_done = 5
  3638. self.assertEqual(move1.product_uom_qty, 15.0)
  3639. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 15.0)
  3640. def test_edit_done_move_line_12(self):
  3641. """ Test that editing a done stock move line linked a tracked product correctly and directly
  3642. adapts the transfer. In this case, we edit the lot to another one, but the original move line
  3643. is not in the default product's UOM.
  3644. """
  3645. lot1 = self.env['stock.lot'].create({
  3646. 'name': 'lot1',
  3647. 'product_id': self.product_lot.id,
  3648. 'company_id': self.env.company.id,
  3649. })
  3650. self.env['stock.lot'].create({
  3651. 'name': 'lot2',
  3652. 'product_id': self.product_lot.id,
  3653. 'company_id': self.env.company.id,
  3654. })
  3655. package1 = self.env['stock.quant.package'].create({'name': 'test_edit_done_move_line_12'})
  3656. move1 = self.env['stock.move'].create({
  3657. 'name': 'test_edit_moveline_1',
  3658. 'location_id': self.supplier_location.id,
  3659. 'location_dest_id': self.stock_location.id,
  3660. 'product_id': self.product_lot.id,
  3661. 'product_uom': self.uom_dozen.id,
  3662. 'product_uom_qty': 1.0,
  3663. })
  3664. move1._action_confirm()
  3665. move1._action_assign()
  3666. move1.move_line_ids.qty_done = 1
  3667. move1.move_line_ids.lot_id = lot1.id
  3668. move1._action_done()
  3669. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1), 12.0)
  3670. # Change the done quantity from 1 dozen to two dozen
  3671. move1.move_line_ids.qty_done = 2
  3672. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1), 24.0)
  3673. def test_edit_done_move_line_13(self):
  3674. """ Test that editing a done stock move line linked to a packed and tracked product correctly
  3675. and directly adapts the transfer. In this case, we edit the lot to another available one
  3676. that we put in the same pack.
  3677. """
  3678. lot1 = self.env['stock.lot'].create({
  3679. 'name': 'lot1',
  3680. 'product_id': self.product_lot.id,
  3681. 'company_id': self.env.company.id,
  3682. })
  3683. lot2 = self.env['stock.lot'].create({
  3684. 'name': 'lot2',
  3685. 'product_id': self.product_lot.id,
  3686. 'company_id': self.env.company.id,
  3687. })
  3688. package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_5'})
  3689. move1 = self.env['stock.move'].create({
  3690. 'name': 'test_edit_moveline_1',
  3691. 'location_id': self.supplier_location.id,
  3692. 'location_dest_id': self.stock_location.id,
  3693. 'product_id': self.product_lot.id,
  3694. 'product_uom': self.uom_unit.id,
  3695. 'product_uom_qty': 1.0,
  3696. })
  3697. move1._action_confirm()
  3698. move1._action_assign()
  3699. move1.move_line_ids.qty_done = 1
  3700. move1.move_line_ids.lot_id = lot1.id
  3701. move1.move_line_ids.result_package_id = package1.id
  3702. move1._action_done()
  3703. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location), 1.0)
  3704. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
  3705. move1.move_line_ids.write({'lot_id': lot2.id})
  3706. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location), 1.0)
  3707. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1), 0.0)
  3708. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot1, package_id=package1), 0.0)
  3709. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_lot, self.stock_location, lot_id=lot2, package_id=package1), 1.0)
  3710. def test_edit_done_move_line_14(self):
  3711. """ Test that editing a done stock move line with a different UoM from its stock move correctly
  3712. updates the quant when its qty and/or its UoM is edited. Also check that we don't allow editing
  3713. a done stock move's UoM.
  3714. """
  3715. move1 = self.env['stock.move'].create({
  3716. 'name': 'test_edit_moveline',
  3717. 'location_id': self.supplier_location.id,
  3718. 'location_dest_id': self.stock_location.id,
  3719. 'product_id': self.product.id,
  3720. 'product_uom': self.uom_unit.id,
  3721. 'product_uom_qty': 12.0,
  3722. })
  3723. move1._action_confirm()
  3724. move1._action_assign()
  3725. move1.move_line_ids.product_uom_id = self.uom_dozen
  3726. move1.move_line_ids.qty_done = 1
  3727. move1._action_done()
  3728. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 12.0)
  3729. move1.move_line_ids.qty_done = 2
  3730. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 24.0)
  3731. self.assertEqual(move1.product_uom_qty, 24.0)
  3732. self.assertEqual(move1.product_qty, 24.0)
  3733. move1.move_line_ids.product_uom_id = self.uom_unit
  3734. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 2.0)
  3735. self.assertEqual(move1.product_uom_qty, 2.0)
  3736. self.assertEqual(move1.product_qty, 2.0)
  3737. with self.assertRaises(UserError):
  3738. move1.product_uom = self.uom_dozen
  3739. def test_immediate_validate_1(self):
  3740. """ In a picking with a single available move, clicking on validate without filling any
  3741. quantities should open a wizard asking to process all the reservation (so, the whole move).
  3742. """
  3743. partner = self.env['res.partner'].create({'name': 'Jean'})
  3744. picking = self.env['stock.picking'].create({
  3745. 'location_id': self.supplier_location.id,
  3746. 'location_dest_id': self.stock_location.id,
  3747. 'partner_id': partner.id,
  3748. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  3749. })
  3750. self.env['stock.move'].create({
  3751. 'name': 'test_immediate_validate_1',
  3752. 'location_id': self.supplier_location.id,
  3753. 'location_dest_id': self.stock_location.id,
  3754. 'picking_id': picking.id,
  3755. 'product_id': self.product.id,
  3756. 'product_uom': self.uom_unit.id,
  3757. 'product_uom_qty': 10.0,
  3758. })
  3759. picking.action_confirm()
  3760. picking.action_assign()
  3761. res_dict = picking.button_validate()
  3762. self.assertEqual(res_dict.get('res_model'), 'stock.immediate.transfer')
  3763. wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save()
  3764. wizard.process()
  3765. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 10.0)
  3766. def test_immediate_validate_2(self):
  3767. """ In a picking with a single partially available move, clicking on validate without
  3768. filling any quantities should open a wizard asking to process all the reservation (so, only
  3769. a part of the initial demand). Validating this wizard should open another one asking for
  3770. the creation of a backorder. If the backorder is created, it should contain the quantities
  3771. not processed.
  3772. """
  3773. partner = self.env['res.partner'].create({'name': 'Jean'})
  3774. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 5.0)
  3775. picking = self.env['stock.picking'].create({
  3776. 'location_id': self.stock_location.id,
  3777. 'location_dest_id': self.customer_location.id,
  3778. 'partner_id': partner.id,
  3779. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  3780. })
  3781. self.env['stock.move'].create({
  3782. 'name': 'test_immediate_validate_2',
  3783. 'location_id': self.stock_location.id,
  3784. 'location_dest_id': self.customer_location.id,
  3785. 'picking_id': picking.id,
  3786. 'product_id': self.product.id,
  3787. 'product_uom': self.uom_unit.id,
  3788. 'product_uom_qty': 10.0,
  3789. })
  3790. picking.action_confirm()
  3791. picking.action_assign()
  3792. # Only 5 products are reserved on the move of 10, click on `button_validate`.
  3793. res_dict = picking.button_validate()
  3794. self.assertEqual(res_dict.get('res_model'), 'stock.immediate.transfer')
  3795. wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save()
  3796. res_dict_for_back_order = wizard.process()
  3797. self.assertEqual(res_dict_for_back_order.get('res_model'), 'stock.backorder.confirmation')
  3798. backorder_wizard = self.env[(res_dict_for_back_order.get('res_model'))].browse(res_dict_for_back_order.get('res_id')).with_context(res_dict_for_back_order['context'])
  3799. # Chose to create a backorder.
  3800. backorder_wizard.process()
  3801. # Only 5 products should be processed on the initial move.
  3802. self.assertEqual(picking.move_ids.state, 'done')
  3803. self.assertEqual(picking.move_ids.quantity_done, 5.0)
  3804. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3805. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  3806. # The backoder should contain a move for the other 5 produts.
  3807. backorder = self.env['stock.picking'].search([('backorder_id', '=', picking.id)])
  3808. self.assertEqual(len(backorder), 1.0)
  3809. self.assertEqual(backorder.move_ids.product_uom_qty, 5.0)
  3810. def test_immediate_validate_3(self):
  3811. """ In a picking with two moves, one partially available and one unavailable, clicking
  3812. on validate without filling any quantities should open a wizard asking to process all the
  3813. reservation (so, only a part of one of the moves). Validating this wizard should open
  3814. another one asking for the creation of a backorder. If the backorder is created, it should
  3815. contain the quantities not processed.
  3816. """
  3817. product5 = self.env['product.product'].create({
  3818. 'name': 'Product 5',
  3819. 'type': 'product',
  3820. 'categ_id': self.env.ref('product.product_category_all').id,
  3821. })
  3822. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1)
  3823. picking = self.env['stock.picking'].create({
  3824. 'location_id': self.stock_location.id,
  3825. 'location_dest_id': self.pack_location.id,
  3826. 'picking_type_id': self.env.ref('stock.picking_type_internal').id,
  3827. })
  3828. product1_move = self.env['stock.move'].create({
  3829. 'name': 'product1_move',
  3830. 'location_id': self.stock_location.id,
  3831. 'location_dest_id': self.pack_location.id,
  3832. 'picking_id': picking.id,
  3833. 'product_id': self.product.id,
  3834. 'product_uom': self.uom_unit.id,
  3835. 'product_uom_qty': 100,
  3836. })
  3837. product5_move = self.env['stock.move'].create({
  3838. 'name': 'product3_move',
  3839. 'location_id': self.stock_location.id,
  3840. 'location_dest_id': self.pack_location.id,
  3841. 'picking_id': picking.id,
  3842. 'product_id': product5.id,
  3843. 'product_uom': self.uom_unit.id,
  3844. 'product_uom_qty': 100,
  3845. })
  3846. picking.action_confirm()
  3847. picking.action_assign()
  3848. # product1_move should be partially available (1/100), product5_move should be totally
  3849. # unavailable (0/100)
  3850. self.assertEqual(product1_move.state, 'partially_available')
  3851. self.assertEqual(product5_move.state, 'confirmed')
  3852. action = picking.button_validate()
  3853. self.assertEqual(action.get('res_model'), 'stock.immediate.transfer')
  3854. wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
  3855. action = wizard.process()
  3856. self.assertTrue(isinstance(action, dict), 'Should open backorder wizard')
  3857. self.assertEqual(action.get('res_model'), 'stock.backorder.confirmation')
  3858. wizard = self.env[(action.get('res_model'))].browse(action.get('res_id')).with_context(action.get('context'))
  3859. wizard.process()
  3860. backorder = self.env['stock.picking'].search([('backorder_id', '=', picking.id)])
  3861. self.assertEqual(len(backorder), 1.0)
  3862. # The backorder should contain 99 product1 and 100 product5.
  3863. for backorder_move in backorder.move_ids:
  3864. if backorder_move.product_id.id == self.product.id:
  3865. self.assertEqual(backorder_move.product_qty, 99)
  3866. elif backorder_move.product_id.id == product5.id:
  3867. self.assertEqual(backorder_move.product_qty, 100)
  3868. def test_immediate_validate_4(self):
  3869. """ In a picking with a single available tracked by lot move, clicking on validate without
  3870. filling any quantities should pop up the immediate transfer wizard.
  3871. """
  3872. partner = self.env['res.partner'].create({'name': 'Jean'})
  3873. lot1 = self.env['stock.lot'].create({
  3874. 'name': 'lot1',
  3875. 'product_id': self.product_lot.id,
  3876. 'company_id': self.env.company.id,
  3877. })
  3878. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 5.0, lot_id=lot1)
  3879. picking = self.env['stock.picking'].create({
  3880. 'location_id': self.stock_location.id,
  3881. 'location_dest_id': self.customer_location.id,
  3882. 'partner_id': partner.id,
  3883. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  3884. })
  3885. # move from shelf1
  3886. self.env['stock.move'].create({
  3887. 'name': 'test_immediate_validate_4',
  3888. 'location_id': self.stock_location.id,
  3889. 'location_dest_id': self.customer_location.id,
  3890. 'picking_id': picking.id,
  3891. 'product_id': self.product_lot.id,
  3892. 'product_uom': self.uom_unit.id,
  3893. 'product_uom_qty': 5.0,
  3894. })
  3895. picking.action_confirm()
  3896. picking.action_assign()
  3897. # No quantities filled, immediate transfer wizard should pop up.
  3898. immediate_trans_wiz_dict = picking.button_validate()
  3899. self.assertEqual(immediate_trans_wiz_dict.get('res_model'), 'stock.immediate.transfer')
  3900. immediate_trans_wiz = Form(self.env[immediate_trans_wiz_dict['res_model']].with_context(immediate_trans_wiz_dict['context'])).save()
  3901. immediate_trans_wiz.process()
  3902. self.assertEqual(picking.move_ids.quantity_done, 5.0)
  3903. # Check move_lines data
  3904. self.assertEqual(len(picking.move_ids.move_line_ids), 1)
  3905. self.assertEqual(picking.move_ids.move_line_ids.lot_id, lot1)
  3906. self.assertEqual(picking.move_ids.move_line_ids.qty_done, 5.0)
  3907. # Check quants data
  3908. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0.0)
  3909. self.assertEqual(len(self.gather_relevant(self.product, self.stock_location)), 0.0)
  3910. def _create_picking_test_immediate_validate_5(self, picking_type_id, product_id):
  3911. picking = self.env['stock.picking'].create({
  3912. 'location_id': self.supplier_location.id,
  3913. 'location_dest_id': self.stock_location.id,
  3914. 'picking_type_id': picking_type_id.id,
  3915. })
  3916. self.env['stock.move'].create({
  3917. 'name': 'move1',
  3918. 'location_id': self.supplier_location.id,
  3919. 'location_dest_id': self.stock_location.id,
  3920. 'picking_id': picking.id,
  3921. 'picking_type_id': picking_type_id.id,
  3922. 'product_id': product_id.id,
  3923. 'product_uom': self.uom_unit.id,
  3924. 'product_uom_qty': 5.0,
  3925. })
  3926. picking.action_confirm()
  3927. for line in picking.move_line_ids:
  3928. line.qty_done = line.reserved_uom_qty
  3929. return picking
  3930. def test_immediate_validate_5(self):
  3931. """ In a receipt with a single tracked by serial numbers move, clicking on validate without
  3932. filling any quantities nor lot should open an UserError except if the picking type is
  3933. configured to allow otherwise.
  3934. """
  3935. picking_type_id = self.env.ref('stock.picking_type_in')
  3936. product_id = self.product_serial
  3937. self.assertTrue(picking_type_id.use_create_lots or picking_type_id.use_existing_lots)
  3938. self.assertEqual(product_id.tracking, 'serial')
  3939. picking = self._create_picking_test_immediate_validate_5(picking_type_id, product_id)
  3940. # should raise because no serial numbers were specified
  3941. self.assertRaises(UserError, picking.button_validate)
  3942. picking_type_id.use_create_lots = False
  3943. picking_type_id.use_existing_lots = False
  3944. picking = self._create_picking_test_immediate_validate_5(picking_type_id, product_id)
  3945. picking.button_validate()
  3946. self.assertEqual(picking.state, 'done')
  3947. def test_immediate_validate_6(self):
  3948. """ In a receipt picking with two moves, one tracked and one untracked, clicking on
  3949. validate without filling any quantities should displays an UserError as long as no quantity
  3950. done and lot_name is set on the tracked move. Now if the user validates the picking, the
  3951. wizard telling the user all reserved quantities will be processed will NOT be opened. This
  3952. wizard is only opene if no quantities were filled. So validating the picking at this state
  3953. will open another wizard asking for the creation of a backorder. Now, if the user processed
  3954. on the second move more than the reservation, a wizard will ask him to confirm.
  3955. """
  3956. picking_type = self.env.ref('stock.picking_type_in')
  3957. picking_type.use_create_lots = True
  3958. picking_type.use_existing_lots = False
  3959. picking = self.env['stock.picking'].create({
  3960. 'location_id': self.supplier_location.id,
  3961. 'location_dest_id': self.stock_location.id,
  3962. 'picking_type_id': picking_type.id,
  3963. })
  3964. self.env['stock.move'].create({
  3965. 'name': 'product1_move',
  3966. 'location_id': self.supplier_location.id,
  3967. 'location_dest_id': self.stock_location.id,
  3968. 'picking_id': picking.id,
  3969. 'product_id': self.product.id,
  3970. 'product_uom': self.uom_unit.id,
  3971. 'product_uom_qty': 1,
  3972. })
  3973. product3_move = self.env['stock.move'].create({
  3974. 'name': 'product3_move',
  3975. 'location_id': self.supplier_location.id,
  3976. 'location_dest_id': self.stock_location.id,
  3977. 'picking_id': picking.id,
  3978. 'product_id': self.product_lot.id,
  3979. 'product_uom': self.uom_unit.id,
  3980. 'product_uom_qty': 1,
  3981. })
  3982. picking.action_confirm()
  3983. picking.action_assign()
  3984. with self.assertRaises(UserError):
  3985. picking.button_validate()
  3986. product3_move.move_line_ids[0].qty_done = 1
  3987. with self.assertRaises(UserError):
  3988. picking.button_validate()
  3989. product3_move.move_line_ids[0].lot_name = '271828'
  3990. action = picking.button_validate() # should open backorder wizard
  3991. self.assertTrue(isinstance(action, dict), 'Should open backorder wizard')
  3992. self.assertEqual(action.get('res_model'), 'stock.backorder.confirmation')
  3993. def test_immediate_validate_7(self):
  3994. """ In a picking with a single unavailable move, clicking on validate without filling any
  3995. quantities should display an UserError telling the user he cannot process a picking without
  3996. any processed quantity.
  3997. """
  3998. partner = self.env['res.partner'].create({'name': 'Jean'})
  3999. picking = self.env['stock.picking'].create({
  4000. 'location_id': self.stock_location.id,
  4001. 'location_dest_id': self.customer_location.id,
  4002. 'partner_id': partner.id,
  4003. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4004. })
  4005. self.env['stock.move'].create({
  4006. 'name': 'test_immediate_validate_2',
  4007. 'location_id': self.stock_location.id,
  4008. 'location_dest_id': self.customer_location.id,
  4009. 'picking_id': picking.id,
  4010. 'product_id': self.product.id,
  4011. 'product_uom': self.uom_unit.id,
  4012. 'product_uom_qty': 10.0,
  4013. })
  4014. picking.action_confirm()
  4015. picking.action_assign()
  4016. scrap = self.env['stock.scrap'].create({
  4017. 'picking_id': picking.id,
  4018. 'product_id': self.product.id,
  4019. 'product_uom_id': self.uom_unit.id,
  4020. 'scrap_qty': 5.0,
  4021. })
  4022. scrap.do_scrap()
  4023. # No products are reserved on the move of 10, click on `button_validate`.
  4024. with self.assertRaises(UserError):
  4025. picking.button_validate()
  4026. def test_immediate_validate_8(self):
  4027. """Validate three receipts at once."""
  4028. partner = self.env['res.partner'].create({'name': 'Pierre'})
  4029. receipt1 = self.env['stock.picking'].create({
  4030. 'location_id': self.supplier_location.id,
  4031. 'location_dest_id': self.stock_location.id,
  4032. 'partner_id': partner.id,
  4033. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4034. })
  4035. self.env['stock.move'].create({
  4036. 'name': 'test_immediate_validate_8_1',
  4037. 'location_id': receipt1.location_id.id,
  4038. 'location_dest_id': receipt1.location_dest_id.id,
  4039. 'picking_id': receipt1.id,
  4040. 'product_id': self.product.id,
  4041. 'product_uom': self.uom_unit.id,
  4042. 'product_uom_qty': 10.0,
  4043. })
  4044. receipt1.action_confirm()
  4045. receipt2 = self.env['stock.picking'].create({
  4046. 'location_id': self.supplier_location.id,
  4047. 'location_dest_id': self.stock_location.id,
  4048. 'partner_id': partner.id,
  4049. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4050. })
  4051. self.env['stock.move'].create({
  4052. 'name': 'test_immediate_validate_8_2',
  4053. 'location_id': receipt2.location_id.id,
  4054. 'location_dest_id': receipt2.location_dest_id.id,
  4055. 'picking_id': receipt2.id,
  4056. 'product_id': self.product.id,
  4057. 'product_uom': self.uom_unit.id,
  4058. 'product_uom_qty': 10.0,
  4059. })
  4060. receipt2.action_confirm()
  4061. receipt3 = self.env['stock.picking'].create({
  4062. 'location_id': self.supplier_location.id,
  4063. 'location_dest_id': self.stock_location.id,
  4064. 'partner_id': partner.id,
  4065. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4066. })
  4067. self.env['stock.move'].create({
  4068. 'name': 'test_immediate_validate_8_3',
  4069. 'location_id': receipt3.location_id.id,
  4070. 'location_dest_id': receipt3.location_dest_id.id,
  4071. 'picking_id': receipt3.id,
  4072. 'product_id': self.product.id,
  4073. 'product_uom': self.uom_unit.id,
  4074. 'product_uom_qty': 10.0,
  4075. })
  4076. receipt3.action_confirm()
  4077. immediate_trans_wiz_dict = (receipt1 + receipt2).button_validate()
  4078. immediate_trans_wiz = Form(self.env[immediate_trans_wiz_dict['res_model']].with_context(immediate_trans_wiz_dict['context'])).save()
  4079. # The different transfers are displayed to the users.
  4080. self.assertTrue(immediate_trans_wiz.show_transfers)
  4081. # All transfers are processed by default
  4082. self.assertEqual(immediate_trans_wiz.immediate_transfer_line_ids.mapped('to_immediate'), [True, True])
  4083. # Only transfer receipt1
  4084. immediate_trans_wiz.immediate_transfer_line_ids.filtered(lambda line: line.picking_id == receipt2).to_immediate = False
  4085. immediate_trans_wiz.process()
  4086. self.assertEqual(receipt1.state, 'done')
  4087. self.assertEqual(receipt2.state, 'assigned')
  4088. # Transfer receipt2 and receipt3.
  4089. immediate_trans_wiz_dict = (receipt3 + receipt2).button_validate()
  4090. immediate_trans_wiz = Form(self.env[immediate_trans_wiz_dict['res_model']].with_context(immediate_trans_wiz_dict['context'])).save()
  4091. immediate_trans_wiz.process()
  4092. self.assertEqual(receipt2.state, 'done')
  4093. self.assertEqual(receipt3.state, 'done')
  4094. def test_immediate_validate_9_tracked_move_with_0_qty_done(self):
  4095. """When trying to validate a picking as an immediate transfer, the done
  4096. quantity of tracked move should be automatically fulfilled if the
  4097. picking type doesn't use new or existing LN/SN."""
  4098. picking_type_receipt = self.env.ref('stock.picking_type_in')
  4099. picking_type_receipt.use_create_lots = False
  4100. picking_type_receipt.use_existing_lots = False
  4101. picking_form = Form(self.env['stock.picking'])
  4102. picking_form.picking_type_id = picking_type_receipt
  4103. with picking_form.move_ids_without_package.new() as move:
  4104. move.product_id = self.product_serial
  4105. move.product_uom_qty = 4
  4106. with picking_form.move_ids_without_package.new() as move:
  4107. move.product_id = self.product_lot
  4108. move.product_uom_qty = 20
  4109. receipt = picking_form.save()
  4110. receipt.action_confirm()
  4111. immediate_wizard = receipt.button_validate()
  4112. immediate_wizard_form = Form(
  4113. self.env[immediate_wizard['res_model']].with_context(immediate_wizard['context'])
  4114. ).save()
  4115. immediate_wizard_form.process()
  4116. self.assertEqual(receipt.state, 'done')
  4117. def test_immediate_validate_10_tracked_move_without_backorder(self):
  4118. """
  4119. Create a picking for a tracked product, validate it as an
  4120. immediate transfer, and ensure that the backorder wizard is
  4121. not triggered when the qty is reserved.
  4122. """
  4123. picking_type_internal = self.env.ref('stock.picking_type_internal')
  4124. picking_type_internal.use_create_lots = True
  4125. picking_type_internal.use_existing_lots = True
  4126. lot = self.env['stock.lot'].create({
  4127. 'name': 'Lot 1',
  4128. 'product_id': self.product_lot.id,
  4129. 'company_id': self.env.company.id,
  4130. })
  4131. self.env['stock.quant']._update_available_quantity(self.product_lot, self.stock_location, 10, lot_id=lot)
  4132. picking_form = Form(self.env['stock.picking'])
  4133. picking_form.picking_type_id = picking_type_internal
  4134. with picking_form.move_ids_without_package.new() as move:
  4135. move.product_id = self.product_lot
  4136. move.product_uom_qty = 4
  4137. internal_transfer = picking_form.save()
  4138. internal_transfer.action_confirm()
  4139. immediate_wizard = internal_transfer.button_validate()
  4140. immediate_wizard_form = Form(
  4141. self.env[immediate_wizard['res_model']].with_context(immediate_wizard['context'])
  4142. ).save()
  4143. immediate_wizard_form.process()
  4144. self.assertEqual(internal_transfer.state, 'done')
  4145. def test_set_quantity_done_1(self):
  4146. move1 = self.env['stock.move'].create({
  4147. 'name': 'test_set_quantity_done_1',
  4148. 'location_id': self.supplier_location.id,
  4149. 'location_dest_id': self.stock_location.id,
  4150. 'product_id': self.product.id,
  4151. 'product_uom': self.uom_unit.id,
  4152. 'product_uom_qty': 2.0,
  4153. })
  4154. move2 = self.env['stock.move'].create({
  4155. 'name': 'test_set_quantity_done_2',
  4156. 'location_id': self.supplier_location.id,
  4157. 'location_dest_id': self.stock_location.id,
  4158. 'product_id': self.product.id,
  4159. 'product_uom': self.uom_unit.id,
  4160. 'product_uom_qty': 2.0,
  4161. })
  4162. (move1 + move2)._action_confirm()
  4163. (move1 + move2).write({'quantity_done': 1})
  4164. self.assertEqual(move1.quantity_done, 1)
  4165. self.assertEqual(move2.quantity_done, 1)
  4166. def test_initial_demand_1(self):
  4167. """ Check that the initial demand is set to 0 when creating a move by hand, and
  4168. that changing the product on the move do not reset the initial demand.
  4169. """
  4170. move1 = self.env['stock.move'].create({
  4171. 'name': 'test_in_1',
  4172. 'location_id': self.supplier_location.id,
  4173. 'location_dest_id': self.stock_location.id,
  4174. 'product_id': self.product.id,
  4175. 'product_uom': self.uom_unit.id,
  4176. })
  4177. self.assertEqual(move1.state, 'draft')
  4178. self.assertEqual(move1.product_uom_qty, 1)
  4179. move1.product_uom_qty = 100
  4180. move1.product_id = self.product_serial
  4181. move1._onchange_product_id()
  4182. self.assertEqual(move1.product_uom_qty, 100)
  4183. def test_scrap_1(self):
  4184. """ Check the created stock move and the impact on quants when we scrap a
  4185. storable product.
  4186. """
  4187. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1)
  4188. scrap_form = Form(self.env['stock.scrap'])
  4189. scrap_form.product_id = self.product
  4190. scrap_form.scrap_qty = 1
  4191. scrap = scrap_form.save()
  4192. scrap.do_scrap()
  4193. self.assertEqual(scrap.state, 'done')
  4194. move = scrap.move_id
  4195. self.assertEqual(move.state, 'done')
  4196. self.assertEqual(move.quantity_done, 1)
  4197. self.assertEqual(move.scrapped, True)
  4198. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4199. def test_scrap_2(self):
  4200. """ Check the created stock move and the impact on quants when we scrap a
  4201. consumable product.
  4202. """
  4203. scrap = self.env['stock.scrap'].create({
  4204. 'product_id': self.product_consu.id,
  4205. 'product_uom_id':self.product_consu.uom_id.id,
  4206. 'scrap_qty': 1,
  4207. })
  4208. self.assertEqual(scrap.name, 'New', 'Name should be New in draft state')
  4209. scrap.do_scrap()
  4210. self.assertTrue(scrap.name.startswith('SP/'), 'Sequence should be Changed after do_scrap')
  4211. self.assertEqual(scrap.state, 'done')
  4212. move = scrap.move_id
  4213. self.assertEqual(move.state, 'done')
  4214. self.assertEqual(move.quantity_done, 1)
  4215. self.assertEqual(move.scrapped, True)
  4216. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product_consu, self.stock_location), 0)
  4217. def test_scrap_3(self):
  4218. """ Scrap the product of a reserved move line. Check that the move line is
  4219. correctly deleted and that the associated stock move is not assigned anymore.
  4220. """
  4221. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1)
  4222. move1 = self.env['stock.move'].create({
  4223. 'name': 'test_scrap_3',
  4224. 'location_id': self.stock_location.id,
  4225. 'location_dest_id': self.customer_location.id,
  4226. 'product_id': self.product.id,
  4227. 'product_uom': self.uom_unit.id,
  4228. 'product_uom_qty': 1.0,
  4229. })
  4230. move1._action_confirm()
  4231. move1._action_assign()
  4232. self.assertEqual(move1.state, 'assigned')
  4233. self.assertEqual(len(move1.move_line_ids), 1)
  4234. scrap = self.env['stock.scrap'].create({
  4235. 'product_id': self.product.id,
  4236. 'product_uom_id':self.product.uom_id.id,
  4237. 'scrap_qty': 1,
  4238. })
  4239. scrap.do_scrap()
  4240. self.assertEqual(move1.state, 'confirmed')
  4241. self.assertEqual(len(move1.move_line_ids), 0)
  4242. def test_scrap_4(self):
  4243. """ Scrap the product of a picking. Then modify the
  4244. done linked stock move and ensure the scrap quantity is also
  4245. updated.
  4246. """
  4247. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 10)
  4248. partner = self.env['res.partner'].create({'name': 'Kimberley'})
  4249. picking = self.env['stock.picking'].create({
  4250. 'name': 'A single picking with one move to scrap',
  4251. 'location_id': self.stock_location.id,
  4252. 'location_dest_id': self.customer_location.id,
  4253. 'partner_id': partner.id,
  4254. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4255. })
  4256. move1 = self.env['stock.move'].create({
  4257. 'name': 'A move to confirm and scrap its product',
  4258. 'location_id': self.stock_location.id,
  4259. 'location_dest_id': self.customer_location.id,
  4260. 'product_id': self.product.id,
  4261. 'product_uom': self.uom_unit.id,
  4262. 'product_uom_qty': 1.0,
  4263. 'picking_id': picking.id,
  4264. })
  4265. move1._action_confirm()
  4266. self.assertEqual(move1.state, 'assigned')
  4267. scrap = self.env['stock.scrap'].create({
  4268. 'product_id': self.product.id,
  4269. 'product_uom_id': self.product.uom_id.id,
  4270. 'scrap_qty': 5,
  4271. 'picking_id': picking.id,
  4272. })
  4273. scrap.action_validate()
  4274. self.assertEqual(len(picking.move_ids), 2)
  4275. scrapped_move = picking.move_ids.filtered(lambda m: m.state == 'done')
  4276. self.assertTrue(scrapped_move, 'No scrapped move created.')
  4277. self.assertEqual(scrapped_move.scrap_ids.ids, [scrap.id], 'Wrong scrap linked to the move.')
  4278. self.assertEqual(scrap.scrap_qty, 5, 'Scrap quantity has been modified and is not correct anymore.')
  4279. scrapped_move.quantity_done = 8
  4280. self.assertEqual(scrap.scrap_qty, 8, 'Scrap quantity is not updated.')
  4281. def test_scrap_5(self):
  4282. """ Scrap the product of a reserved move line where the product is reserved in another
  4283. unit of measure. Check that the move line is correctly updated after the scrap.
  4284. """
  4285. # 4 units are available in stock
  4286. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 4)
  4287. # try to reserve a dozen
  4288. partner = self.env['res.partner'].create({'name': 'Kimberley'})
  4289. picking = self.env['stock.picking'].create({
  4290. 'name': 'A single picking with one move to scrap',
  4291. 'location_id': self.stock_location.id,
  4292. 'location_dest_id': self.customer_location.id,
  4293. 'partner_id': partner.id,
  4294. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4295. })
  4296. move1 = self.env['stock.move'].create({
  4297. 'name': 'A move to confirm and scrap its product',
  4298. 'location_id': self.stock_location.id,
  4299. 'location_dest_id': self.customer_location.id,
  4300. 'product_id': self.product.id,
  4301. 'product_uom': self.uom_dozen.id,
  4302. 'product_uom_qty': 1.0,
  4303. 'picking_id': picking.id,
  4304. })
  4305. move1._action_confirm()
  4306. move1._action_assign()
  4307. self.assertEqual(move1.reserved_availability, 0.33)
  4308. # scrap a unit
  4309. scrap = self.env['stock.scrap'].create({
  4310. 'product_id': self.product.id,
  4311. 'product_uom_id': self.product.uom_id.id,
  4312. 'scrap_qty': 1,
  4313. 'picking_id': picking.id,
  4314. })
  4315. scrap.action_validate()
  4316. self.assertEqual(scrap.state, 'done')
  4317. self.assertEqual(move1.reserved_availability, 0.25)
  4318. def test_scrap_6(self):
  4319. """ Check that scrap correctly handle UoM. """
  4320. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1)
  4321. scrap = self.env['stock.scrap'].create({
  4322. 'product_id': self.product.id,
  4323. 'product_uom_id': self.uom_dozen.id,
  4324. 'scrap_qty': 1,
  4325. })
  4326. warning_message = scrap.action_validate()
  4327. self.assertEqual(warning_message.get('res_model', 'Wrong Model'), 'stock.warn.insufficient.qty.scrap')
  4328. insufficient_qty_wizard = self.env['stock.warn.insufficient.qty.scrap'].create({
  4329. 'product_id': self.product.id,
  4330. 'location_id': self.stock_location.id,
  4331. 'scrap_id': scrap.id,
  4332. 'quantity': 1,
  4333. 'product_uom_name': self.product.uom_id.name
  4334. })
  4335. insufficient_qty_wizard.action_done()
  4336. self.assertEqual(self.env['stock.quant']._gather(self.product, self.stock_location).quantity, -11)
  4337. def test_scrap_7_sn_warning(self):
  4338. """ Check serial numbers are correctly double checked """
  4339. child_loc1 = self.env['stock.location'].create({
  4340. 'name': "child_location1",
  4341. 'usage': 'internal',
  4342. 'location_id': self.stock_location.id
  4343. })
  4344. child_loc2 = self.env['stock.location'].create({
  4345. 'name': "child_location2",
  4346. 'usage': 'internal',
  4347. 'location_id': self.stock_location.id
  4348. })
  4349. lot1 = self.env['stock.lot'].create({
  4350. 'name': 'serial1',
  4351. 'product_id': self.product_serial.id,
  4352. 'company_id': self.env.company.id,
  4353. })
  4354. self.env['stock.quant']._update_available_quantity(self.product_serial, child_loc1, 1, lot1)
  4355. scrap = self.env['stock.scrap'].create({
  4356. 'product_id': self.product_serial.id,
  4357. 'product_uom_id': self.uom_unit.id,
  4358. 'location_id': child_loc2.id,
  4359. 'lot_id': lot1.id
  4360. })
  4361. warning = False
  4362. warning = scrap._onchange_serial_number()
  4363. self.assertTrue(warning, 'Use of wrong serial number location not detected')
  4364. self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
  4365. self.assertEqual(scrap.location_id, child_loc1, 'Location was not auto-corrected')
  4366. def test_scrap_8(self):
  4367. """
  4368. Suppose a user wants to scrap some products thanks to internal moves.
  4369. This test checks the state of the picking based on few cases
  4370. """
  4371. scrap_location = self.env['stock.location'].search([('company_id', '=', self.env.company.id), ('scrap_location', '=', True)], limit=1)
  4372. internal_operation = self.env['stock.picking.type'].with_context(active_test=False).search([('code', '=', 'internal'), ('company_id', '=', self.env.company.id)], limit=1)
  4373. internal_operation.active = True
  4374. product01 = self.product
  4375. product02 = self.env['product.product'].create({
  4376. 'name': 'SuperProduct',
  4377. 'type': 'product',
  4378. })
  4379. self.env['stock.quant']._update_available_quantity(product01, self.stock_location, 3)
  4380. self.env['stock.quant']._update_available_quantity(product02, self.stock_location, 1)
  4381. scrap_picking01, scrap_picking02, scrap_picking03 = self.env['stock.picking'].create([{
  4382. 'location_id': self.stock_location.id,
  4383. 'location_dest_id': scrap_location.id,
  4384. 'picking_type_id': internal_operation.id,
  4385. 'move_ids': [(0, 0, {
  4386. 'name': 'Scrap %s' % product.display_name,
  4387. 'location_id': self.stock_location.id,
  4388. 'location_dest_id': scrap_location.id,
  4389. 'product_id': product.id,
  4390. 'product_uom': product.uom_id.id,
  4391. 'product_uom_qty': 1.0,
  4392. 'picking_type_id': internal_operation.id,
  4393. }) for product in products],
  4394. } for products in [(product01,), (product01,), (product01, product02)]])
  4395. (scrap_picking01 + scrap_picking02 + scrap_picking03).action_confirm()
  4396. # All SM are processed
  4397. scrap_picking01.move_ids.quantity_done = 1
  4398. scrap_picking01.button_validate()
  4399. # All SM are cancelled
  4400. scrap_picking02.action_cancel()
  4401. # Process one SM and cancel the other one
  4402. pick03_prod01_move = scrap_picking03.move_ids.filtered(lambda sm: sm.product_id == product01)
  4403. pick03_prod02_move = scrap_picking03.move_ids - pick03_prod01_move
  4404. pick03_prod01_move.quantity_done = 1
  4405. pick03_prod02_move._action_cancel()
  4406. scrap_picking03.button_validate()
  4407. self.assertEqual(scrap_picking01.move_ids.state, 'done')
  4408. self.assertEqual(scrap_picking01.state, 'done')
  4409. self.assertEqual(scrap_picking02.move_ids.state, 'cancel')
  4410. self.assertEqual(scrap_picking02.state, 'cancel')
  4411. self.assertEqual(pick03_prod01_move.state, 'done')
  4412. self.assertEqual(pick03_prod02_move.state, 'cancel')
  4413. self.assertEqual(scrap_picking03.state, 'done')
  4414. self.assertEqual(self.env['stock.quant']._get_available_quantity(product01, self.stock_location), 1)
  4415. self.assertEqual(self.env['stock.quant']._get_available_quantity(product02, self.stock_location), 1)
  4416. def test_in_date_1(self):
  4417. """ Check that moving a tracked quant keeps the incoming date.
  4418. """
  4419. move1 = self.env['stock.move'].create({
  4420. 'name': 'test_in_date_1',
  4421. 'location_id': self.supplier_location.id,
  4422. 'location_dest_id': self.stock_location.id,
  4423. 'product_id': self.product_lot.id,
  4424. 'product_uom': self.uom_unit.id,
  4425. 'product_uom_qty': 1.0,
  4426. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4427. })
  4428. move1._action_confirm()
  4429. move1._action_assign()
  4430. move1.move_line_ids.lot_name = 'lot1'
  4431. move1.move_line_ids.qty_done = 1
  4432. move1._action_done()
  4433. quant = self.gather_relevant(self.product_lot, self.stock_location)
  4434. self.assertEqual(len(quant), 1.0)
  4435. self.assertNotEqual(quant.in_date, False)
  4436. # Keep a reference to the initial incoming date in order to compare it later.
  4437. initial_incoming_date = quant.in_date
  4438. move2 = self.env['stock.move'].create({
  4439. 'name': 'test_in_date_1',
  4440. 'location_id': self.stock_location.id,
  4441. 'location_dest_id': self.pack_location.id,
  4442. 'product_id': self.product_lot.id,
  4443. 'product_uom': self.uom_unit.id,
  4444. 'product_uom_qty': 1.0,
  4445. })
  4446. move2._action_confirm()
  4447. move2._action_assign()
  4448. move2.move_line_ids.qty_done = 1
  4449. move2._action_done()
  4450. quant = self.gather_relevant(self.product_lot, self.pack_location)
  4451. self.assertEqual(len(quant), 1.0)
  4452. self.assertEqual(quant.in_date, initial_incoming_date)
  4453. def test_in_date_2(self):
  4454. """ Check that editing a done move line for a tracked product and changing its lot
  4455. correctly restores the original lot with its incoming date and remove the new lot
  4456. with its incoming date.
  4457. """
  4458. lot1 = self.env['stock.lot'].create({
  4459. 'name': 'lot1',
  4460. 'product_id': self.product_lot.id,
  4461. 'company_id': self.env.company.id,
  4462. })
  4463. lot2 = self.env['stock.lot'].create({
  4464. 'name': 'lot2',
  4465. 'product_id': self.product_lot.id,
  4466. 'company_id': self.env.company.id,
  4467. })
  4468. # receive lot1
  4469. move1 = self.env['stock.move'].create({
  4470. 'name': 'test_in_date_1',
  4471. 'location_id': self.supplier_location.id,
  4472. 'location_dest_id': self.stock_location.id,
  4473. 'product_id': self.product_lot.id,
  4474. 'product_uom': self.uom_unit.id,
  4475. 'product_uom_qty': 1.0,
  4476. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4477. })
  4478. move1._action_confirm()
  4479. move1._action_assign()
  4480. move1.move_line_ids.lot_id = lot1
  4481. move1.move_line_ids.qty_done = 1
  4482. move1._action_done()
  4483. # receive lot2
  4484. move2 = self.env['stock.move'].create({
  4485. 'name': 'test_in_date_1',
  4486. 'location_id': self.supplier_location.id,
  4487. 'location_dest_id': self.stock_location.id,
  4488. 'product_id': self.product_lot.id,
  4489. 'product_uom': self.uom_unit.id,
  4490. 'product_uom_qty': 1.0,
  4491. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4492. })
  4493. move2._action_confirm()
  4494. move2._action_assign()
  4495. move2.move_line_ids.lot_id = lot2
  4496. move2.move_line_ids.qty_done = 1
  4497. move2._action_done()
  4498. initial_in_date_lot2 = self.env['stock.quant'].search([
  4499. ('location_id', '=', self.stock_location.id),
  4500. ('product_id', '=', self.product_lot.id),
  4501. ('lot_id', '=', lot2.id),
  4502. ]).in_date
  4503. # Edit lot1's incoming date.
  4504. quant_lot1 = self.env['stock.quant'].search([
  4505. ('location_id', '=', self.stock_location.id),
  4506. ('product_id', '=', self.product_lot.id),
  4507. ('lot_id', '=', lot1.id),
  4508. ])
  4509. from odoo.fields import Datetime
  4510. from datetime import timedelta
  4511. initial_in_date_lot1 = Datetime.now() - timedelta(days=5)
  4512. quant_lot1.in_date = initial_in_date_lot1
  4513. # Move one quant to pack location
  4514. move3 = self.env['stock.move'].create({
  4515. 'name': 'test_in_date_1',
  4516. 'location_id': self.stock_location.id,
  4517. 'location_dest_id': self.pack_location.id,
  4518. 'product_id': self.product_lot.id,
  4519. 'product_uom': self.uom_unit.id,
  4520. 'product_uom_qty': 1.0,
  4521. })
  4522. move3._action_confirm()
  4523. move3._action_assign()
  4524. move3.move_line_ids.qty_done = 1
  4525. move3._action_done()
  4526. quant_in_pack = self.env['stock.quant'].search([
  4527. ('product_id', '=', self.product_lot.id),
  4528. ('location_id', '=', self.pack_location.id),
  4529. ])
  4530. # As lot1 has an older date and FIFO is set by default, it's the one that should be
  4531. # in pack.
  4532. self.assertEqual(len(quant_in_pack), 1)
  4533. self.assertAlmostEqual(quant_in_pack.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
  4534. self.assertEqual(quant_in_pack.lot_id, lot1)
  4535. # Now, edit the move line and actually move the other lot
  4536. move3.move_line_ids.lot_id = lot2
  4537. # Check that lot1 correctly is back to stock with its right in_date
  4538. quant_lot1 = self.env['stock.quant'].search([
  4539. ('location_id.usage', '=', 'internal'),
  4540. ('product_id', '=', self.product_lot.id),
  4541. ('lot_id', '=', lot1.id),
  4542. ('quantity', '!=', 0),
  4543. ])
  4544. self.assertEqual(quant_lot1.location_id, self.stock_location)
  4545. self.assertAlmostEqual(quant_lot1.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
  4546. # Check that lo2 is in pack with is right in_date
  4547. quant_lot2 = self.env['stock.quant'].search([
  4548. ('location_id.usage', '=', 'internal'),
  4549. ('product_id', '=', self.product_lot.id),
  4550. ('lot_id', '=', lot2.id),
  4551. ('quantity', '!=', 0),
  4552. ])
  4553. self.assertEqual(quant_lot2.location_id, self.pack_location)
  4554. self.assertAlmostEqual(quant_lot2.in_date, initial_in_date_lot2, delta=timedelta(seconds=1))
  4555. def test_in_date_3(self):
  4556. """ Check that, when creating a move line on a done stock move, the lot and its incoming
  4557. date are correctly moved to the destination location.
  4558. """
  4559. lot1 = self.env['stock.lot'].create({
  4560. 'name': 'lot1',
  4561. 'product_id': self.product_lot.id,
  4562. 'company_id': self.env.company.id,
  4563. })
  4564. lot2 = self.env['stock.lot'].create({
  4565. 'name': 'lot2',
  4566. 'product_id': self.product_lot.id,
  4567. 'company_id': self.env.company.id,
  4568. })
  4569. # receive lot1
  4570. move1 = self.env['stock.move'].create({
  4571. 'name': 'test_in_date_1',
  4572. 'location_id': self.supplier_location.id,
  4573. 'location_dest_id': self.stock_location.id,
  4574. 'product_id': self.product_lot.id,
  4575. 'product_uom': self.uom_unit.id,
  4576. 'product_uom_qty': 1.0,
  4577. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4578. })
  4579. move1._action_confirm()
  4580. move1._action_assign()
  4581. move1.move_line_ids.lot_id = lot1
  4582. move1.move_line_ids.qty_done = 1
  4583. move1._action_done()
  4584. # receive lot2
  4585. move2 = self.env['stock.move'].create({
  4586. 'name': 'test_in_date_1',
  4587. 'location_id': self.supplier_location.id,
  4588. 'location_dest_id': self.stock_location.id,
  4589. 'product_id': self.product_lot.id,
  4590. 'product_uom': self.uom_unit.id,
  4591. 'product_uom_qty': 1.0,
  4592. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4593. })
  4594. move2._action_confirm()
  4595. move2._action_assign()
  4596. move2.move_line_ids.lot_id = lot2
  4597. move2.move_line_ids.qty_done = 1
  4598. move2._action_done()
  4599. initial_in_date_lot2 = self.env['stock.quant'].search([
  4600. ('location_id', '=', self.stock_location.id),
  4601. ('product_id', '=', self.product_lot.id),
  4602. ('lot_id', '=', lot2.id),
  4603. ('quantity', '!=', 0),
  4604. ]).in_date
  4605. # Edit lot1's incoming date.
  4606. quant_lot1 = self.env['stock.quant'].search([
  4607. ('location_id.usage', '=', 'internal'),
  4608. ('product_id', '=', self.product_lot.id),
  4609. ('lot_id', '=', lot1.id),
  4610. ('quantity', '!=', 0),
  4611. ])
  4612. from odoo.fields import Datetime
  4613. from datetime import timedelta
  4614. initial_in_date_lot1 = Datetime.now() - timedelta(days=5)
  4615. quant_lot1.in_date = initial_in_date_lot1
  4616. # Move one quant to pack location
  4617. move3 = self.env['stock.move'].create({
  4618. 'name': 'test_in_date_1',
  4619. 'location_id': self.stock_location.id,
  4620. 'location_dest_id': self.pack_location.id,
  4621. 'product_id': self.product_lot.id,
  4622. 'product_uom': self.uom_unit.id,
  4623. 'product_uom_qty': 1.0,
  4624. })
  4625. move3._action_confirm()
  4626. move3._action_assign()
  4627. move3.move_line_ids.qty_done = 1
  4628. move3._action_done()
  4629. # Now, also move lot2
  4630. self.env['stock.move.line'].create({
  4631. 'move_id': move3.id,
  4632. 'product_id': move3.product_id.id,
  4633. 'qty_done': 1,
  4634. 'product_uom_id': move3.product_uom.id,
  4635. 'location_id': move3.location_id.id,
  4636. 'location_dest_id': move3.location_dest_id.id,
  4637. 'lot_id': lot2.id,
  4638. })
  4639. quants = self.env['stock.quant'].search([
  4640. ('location_id.usage', '=', 'internal'),
  4641. ('product_id', '=', self.product_lot.id),
  4642. ('quantity', '!=', 0),
  4643. ])
  4644. self.assertEqual(len(quants), 2)
  4645. for quant in quants:
  4646. if quant.lot_id == lot1:
  4647. self.assertAlmostEqual(quant.in_date, initial_in_date_lot1, delta=timedelta(seconds=1))
  4648. elif quant.lot_id == lot2:
  4649. self.assertAlmostEqual(quant.in_date, initial_in_date_lot2, delta=timedelta(seconds=1))
  4650. def test_edit_initial_demand_1(self):
  4651. """ Increase initial demand once everything is reserved and check if
  4652. the existing move_line is updated.
  4653. """
  4654. move1 = self.env['stock.move'].create({
  4655. 'name': 'test_transit_1',
  4656. 'location_id': self.supplier_location.id,
  4657. 'location_dest_id': self.stock_location.id,
  4658. 'product_id': self.product.id,
  4659. 'product_uom': self.uom_unit.id,
  4660. 'product_uom_qty': 10.0,
  4661. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4662. })
  4663. move1._action_confirm()
  4664. move1._action_assign()
  4665. move1.product_uom_qty = 15
  4666. # _action_assign is automatically called
  4667. self.assertEqual(move1.state, 'assigned')
  4668. self.assertEqual(move1.product_uom_qty, 15)
  4669. self.assertEqual(len(move1.move_line_ids), 1)
  4670. def test_edit_initial_demand_2(self):
  4671. """ Decrease initial demand once everything is reserved and check if
  4672. the existing move_line has been dropped after the updated and another
  4673. is created once the move is reserved.
  4674. """
  4675. move1 = self.env['stock.move'].create({
  4676. 'name': 'test_transit_1',
  4677. 'location_id': self.supplier_location.id,
  4678. 'location_dest_id': self.stock_location.id,
  4679. 'product_id': self.product.id,
  4680. 'product_uom': self.uom_unit.id,
  4681. 'product_uom_qty': 10.0,
  4682. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4683. })
  4684. move1._action_confirm()
  4685. move1._action_assign()
  4686. self.assertEqual(move1.state, 'assigned')
  4687. move1.product_uom_qty = 5
  4688. self.assertEqual(move1.state, 'assigned')
  4689. self.assertEqual(move1.product_uom_qty, 5)
  4690. self.assertEqual(len(move1.move_line_ids), 1)
  4691. def test_initial_demand_3(self):
  4692. """ Increase the initial demand on a receipt picking, the system should automatically
  4693. reserve the new quantity.
  4694. """
  4695. picking = self.env['stock.picking'].create({
  4696. 'location_id': self.supplier_location.id,
  4697. 'location_dest_id': self.stock_location.id,
  4698. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4699. 'immediate_transfer': True,
  4700. })
  4701. move1 = self.env['stock.move'].create({
  4702. 'name': 'test_transit_1',
  4703. 'location_id': self.supplier_location.id,
  4704. 'location_dest_id': self.stock_location.id,
  4705. 'product_id': self.product.id,
  4706. 'product_uom': self.uom_unit.id,
  4707. 'quantity_done': 10.0,
  4708. 'picking_id': picking.id,
  4709. })
  4710. picking._autoconfirm_picking()
  4711. self.assertEqual(picking.state, 'assigned')
  4712. move1.quantity_done = 12
  4713. self.assertEqual(picking.state, 'assigned')
  4714. def test_initial_demand_4(self):
  4715. """ Increase the initial demand on a delivery picking, the system should not automatically
  4716. reserve the new quantity.
  4717. """
  4718. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 12)
  4719. picking = self.env['stock.picking'].create({
  4720. 'location_id': self.stock_location.id,
  4721. 'location_dest_id': self.customer_location.id,
  4722. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4723. })
  4724. move1 = self.env['stock.move'].create({
  4725. 'name': 'test_transit_1',
  4726. 'location_id': self.stock_location.id,
  4727. 'location_dest_id': self.customer_location.id,
  4728. 'product_id': self.product.id,
  4729. 'product_uom': self.uom_unit.id,
  4730. 'product_uom_qty': 10.0,
  4731. 'picking_id': picking.id,
  4732. })
  4733. picking.action_confirm()
  4734. picking.action_assign()
  4735. self.assertEqual(picking.state, 'assigned')
  4736. move1.product_uom_qty = 12
  4737. self.assertEqual(picking.state, 'assigned') # actually, partially available
  4738. self.assertEqual(move1.state, 'partially_available')
  4739. picking.action_assign()
  4740. self.assertEqual(move1.state, 'assigned')
  4741. def test_change_product_type(self):
  4742. """ Changing type of an existing product will raise a user error if
  4743. - some move are reserved
  4744. - switching from a stockable product when qty_available is not zero
  4745. - switching the product type when there are already done moves
  4746. """
  4747. move_in = self.env['stock.move'].create({
  4748. 'name': 'test_customer',
  4749. 'location_id': self.customer_location.id,
  4750. 'location_dest_id': self.stock_location.id,
  4751. 'product_id': self.product.id,
  4752. 'product_uom': self.uom_unit.id,
  4753. 'product_uom_qty': 5,
  4754. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4755. })
  4756. move_in._action_confirm()
  4757. move_in._action_assign()
  4758. # Check raise UserError(_("You can not change the type of a product that is currently reserved on a stock
  4759. with self.assertRaises(UserError):
  4760. self.product.detailed_type = 'consu'
  4761. move_in._action_cancel()
  4762. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 10)
  4763. # Check raise UserError(_("Available quantity should be set to zero before changing detailed_type"))
  4764. with self.assertRaises(UserError):
  4765. self.product.detailed_type = 'consu'
  4766. move_out = self.env['stock.move'].create({
  4767. 'name': 'test_customer',
  4768. 'location_id': self.stock_location.id,
  4769. 'location_dest_id': self.customer_location.id,
  4770. 'product_id': self.product.id,
  4771. 'product_uom': self.uom_unit.id,
  4772. 'product_uom_qty': self.product.qty_available,
  4773. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4774. })
  4775. move_out._action_confirm()
  4776. move_out._action_assign()
  4777. move_out.quantity_done = self.product.qty_available
  4778. move_out._action_done()
  4779. # Check raise UserError(_("You can not change the type of a product that was already used."))
  4780. with self.assertRaises(UserError):
  4781. self.product.detailed_type = 'consu'
  4782. move2 = self.env['stock.move'].create({
  4783. 'name': 'test_customer',
  4784. 'location_id': self.stock_location.id,
  4785. 'location_dest_id': self.customer_location.id,
  4786. 'product_id': self.product.id,
  4787. 'product_uom': self.uom_unit.id,
  4788. 'product_uom_qty': 5,
  4789. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4790. })
  4791. move2._action_confirm()
  4792. move2._action_assign()
  4793. with self.assertRaises(UserError):
  4794. self.product.detailed_type = 'consu'
  4795. move2._action_cancel()
  4796. with self.assertRaises(UserError):
  4797. self.product.detailed_type = 'consu'
  4798. def test_edit_done_picking_1(self):
  4799. """ Add a new move line in a done picking should generate an
  4800. associated move.
  4801. """
  4802. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 12)
  4803. picking = self.env['stock.picking'].create({
  4804. 'location_id': self.stock_location.id,
  4805. 'location_dest_id': self.customer_location.id,
  4806. 'picking_type_id': self.env.ref('stock.picking_type_in').id,
  4807. })
  4808. move1 = self.env['stock.move'].create({
  4809. 'name': 'test_transit_1',
  4810. 'location_id': self.stock_location.id,
  4811. 'location_dest_id': self.customer_location.id,
  4812. 'product_id': self.product.id,
  4813. 'product_uom': self.uom_unit.id,
  4814. 'product_uom_qty': 10.0,
  4815. 'picking_id': picking.id,
  4816. })
  4817. picking.action_confirm()
  4818. picking.action_assign()
  4819. move1.quantity_done = 10
  4820. picking._action_done()
  4821. self.assertEqual(len(picking.move_ids), 1, 'One move should exist for the picking.')
  4822. self.assertEqual(len(picking.move_line_ids), 1, 'One move line should exist for the picking.')
  4823. ml = self.env['stock.move.line'].create({
  4824. 'location_id': self.stock_location.id,
  4825. 'location_dest_id': self.customer_location.id,
  4826. 'product_id': self.product.id,
  4827. 'product_uom_id': self.uom_unit.id,
  4828. 'qty_done': 2.0,
  4829. 'picking_id': picking.id,
  4830. })
  4831. self.assertEqual(len(picking.move_ids), 2, 'The new move associated to the move line does not exist.')
  4832. self.assertEqual(len(picking.move_line_ids), 2, 'It should be 2 move lines for the picking.')
  4833. self.assertTrue(ml.move_id in picking.move_ids, 'Links are not correct between picking, moves and move lines.')
  4834. self.assertEqual(picking.state, 'done', 'Picking should still done after adding a new move line.')
  4835. self.assertTrue(all(move.state == 'done' for move in picking.move_ids), 'Wrong state for move.')
  4836. def test_put_in_pack_1(self):
  4837. """ Check that reserving a move and adding its move lines to
  4838. different packages work as expected.
  4839. """
  4840. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 2)
  4841. picking = self.env['stock.picking'].create({
  4842. 'location_id': self.stock_location.id,
  4843. 'location_dest_id': self.customer_location.id,
  4844. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4845. })
  4846. move1 = self.env['stock.move'].create({
  4847. 'name': 'test_transit_1',
  4848. 'location_id': self.stock_location.id,
  4849. 'location_dest_id': self.customer_location.id,
  4850. 'product_id': self.product.id,
  4851. 'product_uom': self.uom_unit.id,
  4852. 'product_uom_qty': 2.0,
  4853. 'picking_id': picking.id,
  4854. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4855. })
  4856. picking.action_confirm()
  4857. picking.action_assign()
  4858. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4859. move1.quantity_done = 1
  4860. picking.action_put_in_pack()
  4861. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4862. self.assertEqual(len(picking.move_line_ids), 2)
  4863. unpacked_ml = picking.move_line_ids.filtered(lambda ml: not ml.result_package_id)
  4864. self.assertEqual(unpacked_ml.reserved_qty, 1)
  4865. unpacked_ml.qty_done = 1
  4866. picking.action_put_in_pack()
  4867. self.assertEqual(len(picking.move_line_ids), 2)
  4868. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4869. picking.button_validate()
  4870. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4871. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 2)
  4872. def test_put_in_pack_2(self):
  4873. """Check that reserving moves without done quantity
  4874. adding in same package.
  4875. """
  4876. product1 = self.env['product.product'].create({
  4877. 'name': 'Product B',
  4878. 'type': 'product',
  4879. 'categ_id': self.env.ref('product.product_category_all').id,
  4880. })
  4881. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1)
  4882. self.env['stock.quant']._update_available_quantity(product1, self.stock_location, 2)
  4883. picking = self.env['stock.picking'].create({
  4884. 'location_id': self.stock_location.id,
  4885. 'location_dest_id': self.customer_location.id,
  4886. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4887. })
  4888. move1 = self.env['stock.move'].create({
  4889. 'name': 'test_transit_1',
  4890. 'location_id': self.stock_location.id,
  4891. 'location_dest_id': self.customer_location.id,
  4892. 'product_id': self.product.id,
  4893. 'product_uom': self.uom_unit.id,
  4894. 'product_uom_qty': 1.0,
  4895. 'picking_id': picking.id,
  4896. })
  4897. move2 = self.env['stock.move'].create({
  4898. 'name': 'test_transit_2',
  4899. 'location_id': self.stock_location.id,
  4900. 'location_dest_id': self.customer_location.id,
  4901. 'product_id': product1.id,
  4902. 'product_uom': self.uom_unit.id,
  4903. 'product_uom_qty': 2.0,
  4904. 'picking_id': picking.id,
  4905. })
  4906. picking.action_confirm()
  4907. picking.action_assign()
  4908. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4909. self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, self.stock_location), 0)
  4910. picking.action_put_in_pack()
  4911. self.assertEqual(len(picking.move_line_ids), 2)
  4912. self.assertEqual(picking.move_line_ids[0].qty_done, 1, "Stock move line should have 1 quantity as a done quantity.")
  4913. self.assertEqual(picking.move_line_ids[1].qty_done, 2, "Stock move line should have 2 quantity as a done quantity.")
  4914. line1_result_package = picking.move_line_ids[0].result_package_id
  4915. line2_result_package = picking.move_line_ids[1].result_package_id
  4916. self.assertEqual(line1_result_package, line2_result_package, "Product and Product1 should be in a same package.")
  4917. def test_put_in_pack_3(self):
  4918. """Check that one reserving move without done quantity and
  4919. another reserving move with done quantity adding in different
  4920. package.
  4921. """
  4922. product1 = self.env['product.product'].create({
  4923. 'name': 'Product B',
  4924. 'type': 'product',
  4925. 'categ_id': self.env.ref('product.product_category_all').id,
  4926. })
  4927. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 1)
  4928. self.env['stock.quant']._update_available_quantity(product1, self.stock_location, 2)
  4929. picking = self.env['stock.picking'].create({
  4930. 'location_id': self.stock_location.id,
  4931. 'location_dest_id': self.customer_location.id,
  4932. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4933. })
  4934. move1 = self.env['stock.move'].create({
  4935. 'name': 'test_transit_1',
  4936. 'location_id': self.stock_location.id,
  4937. 'location_dest_id': self.customer_location.id,
  4938. 'product_id': self.product.id,
  4939. 'product_uom': self.uom_unit.id,
  4940. 'product_uom_qty': 1.0,
  4941. 'picking_id': picking.id,
  4942. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4943. })
  4944. move2 = self.env['stock.move'].create({
  4945. 'name': 'test_transit_2',
  4946. 'location_id': self.stock_location.id,
  4947. 'location_dest_id': self.customer_location.id,
  4948. 'product_id': product1.id,
  4949. 'product_uom': self.uom_unit.id,
  4950. 'product_uom_qty': 2.0,
  4951. 'picking_id': picking.id,
  4952. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  4953. })
  4954. picking.action_confirm()
  4955. picking.action_assign()
  4956. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 0)
  4957. self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, self.stock_location), 0)
  4958. move1.quantity_done = 1
  4959. picking.action_put_in_pack()
  4960. move2.quantity_done = 2
  4961. picking.action_put_in_pack()
  4962. self.assertEqual(len(picking.move_line_ids), 2)
  4963. line1_result_package = picking.move_line_ids[0].result_package_id
  4964. line2_result_package = picking.move_line_ids[1].result_package_id
  4965. self.assertNotEqual(line1_result_package, line2_result_package, "Product and Product1 should be in a different package.")
  4966. def test_move_line_aggregated_product_quantities(self):
  4967. """ Test the `stock.move.line` method `_get_aggregated_product_quantities`,
  4968. who returns data used to print delivery slips.
  4969. """
  4970. # Creates two other products.
  4971. product2 = self.env['product.product'].create({
  4972. 'name': 'Product B',
  4973. 'type': 'product',
  4974. 'categ_id': self.env.ref('product.product_category_all').id,
  4975. })
  4976. product3 = self.env['product.product'].create({
  4977. 'name': 'Product C',
  4978. 'type': 'product',
  4979. 'categ_id': self.env.ref('product.product_category_all').id,
  4980. })
  4981. # Adds some quantity on stock.
  4982. self.env['stock.quant'].with_context(inventory_mode=True).create([{
  4983. 'product_id': self.product.id,
  4984. 'inventory_quantity': 100,
  4985. 'location_id': self.stock_location.id,
  4986. }, {
  4987. 'product_id': product2.id,
  4988. 'inventory_quantity': 100,
  4989. 'location_id': self.stock_location.id,
  4990. }, {
  4991. 'product_id': product3.id,
  4992. 'inventory_quantity': 100,
  4993. 'location_id': self.stock_location.id,
  4994. }]).action_apply_inventory()
  4995. # Creates a delivery for a bunch of products.
  4996. delivery_form = Form(self.env['stock.picking'])
  4997. delivery_form.picking_type_id = self.env.ref('stock.picking_type_out')
  4998. with delivery_form.move_ids_without_package.new() as move:
  4999. move.product_id = self.product
  5000. move.product_uom_qty = 10
  5001. with delivery_form.move_ids_without_package.new() as move:
  5002. move.product_id = product2
  5003. move.product_uom_qty = 10
  5004. with delivery_form.move_ids_without_package.new() as move:
  5005. move.product_id = product3
  5006. move.product_uom_qty = 10
  5007. delivery = delivery_form.save()
  5008. delivery.action_confirm()
  5009. # Delivers a part of the quantity, creates a backorder for the remaining qty.
  5010. delivery.move_line_ids.filtered(lambda ml: ml.product_id == self.product).qty_done = 6
  5011. delivery.move_line_ids.filtered(lambda ml: ml.product_id == product2).qty_done = 2
  5012. backorder_wizard_dict = delivery.button_validate()
  5013. backorder_wizard_form = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context']))
  5014. backorder_wizard_form.save().process() # Creates the backorder.
  5015. first_backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery.id)], limit=1)
  5016. # Checks the values.
  5017. aggregate_values = delivery.move_line_ids._get_aggregated_product_quantities()
  5018. self.assertEqual(len(aggregate_values), 2)
  5019. sml1 = delivery.move_line_ids.filtered(lambda ml: ml.product_id == self.product)
  5020. sml2 = delivery.move_line_ids.filtered(lambda ml: ml.product_id == product2)
  5021. aggregate_val_1 = aggregate_values[f'{self.product.id}_{self.product.name}__{sml1.product_uom_id.id}']
  5022. aggregate_val_2 = aggregate_values[f'{product2.id}_{product2.name}__{sml2.product_uom_id.id}']
  5023. self.assertEqual(aggregate_val_1['qty_ordered'], 10)
  5024. self.assertEqual(aggregate_val_1['qty_done'], 6)
  5025. self.assertEqual(aggregate_val_2['qty_ordered'], 10)
  5026. self.assertEqual(aggregate_val_2['qty_done'], 2)
  5027. # Delivers a part of the BO's qty., and creates an another backorder.
  5028. first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == self.product).qty_done = 4
  5029. first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product2).qty_done = 6
  5030. first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product3).qty_done = 7
  5031. backorder_wizard_dict = first_backorder.button_validate()
  5032. backorder_wizard_form = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context']))
  5033. backorder_wizard_form.save().process() # Creates the backorder.
  5034. second_backorder = self.env['stock.picking'].search([('backorder_id', '=', first_backorder.id)], limit=1)
  5035. # Checks the values for the original delivery.
  5036. aggregate_values = delivery.move_line_ids._get_aggregated_product_quantities()
  5037. self.assertEqual(len(aggregate_values), 2)
  5038. sml1 = delivery.move_line_ids.filtered(lambda ml: ml.product_id == self.product)
  5039. sml2 = delivery.move_line_ids.filtered(lambda ml: ml.product_id == product2)
  5040. aggregate_val_1 = aggregate_values[f'{self.product.id}_{self.product.name}__{sml1.product_uom_id.id}']
  5041. aggregate_val_2 = aggregate_values[f'{product2.id}_{product2.name}__{sml2.product_uom_id.id}']
  5042. self.assertEqual(aggregate_val_1['qty_ordered'], 10)
  5043. self.assertEqual(aggregate_val_1['qty_done'], 6)
  5044. self.assertEqual(aggregate_val_2['qty_ordered'], 10)
  5045. self.assertEqual(aggregate_val_2['qty_done'], 2)
  5046. # Checks the values for the first back order.
  5047. aggregate_values = first_backorder.move_line_ids._get_aggregated_product_quantities()
  5048. self.assertEqual(len(aggregate_values), 3)
  5049. sml1 = first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == self.product)
  5050. sml2 = first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product2)
  5051. sml3 = first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product3)
  5052. aggregate_val_1 = aggregate_values[f'{self.product.id}_{self.product.name}__{sml1.product_uom_id.id}']
  5053. aggregate_val_2 = aggregate_values[f'{product2.id}_{product2.name}__{sml2.product_uom_id.id}']
  5054. aggregate_val_3 = aggregate_values[f'{product3.id}_{product3.name}__{sml3.product_uom_id.id}']
  5055. self.assertEqual(aggregate_val_1['qty_ordered'], 4)
  5056. self.assertEqual(aggregate_val_1['qty_done'], 4)
  5057. self.assertEqual(aggregate_val_2['qty_ordered'], 8)
  5058. self.assertEqual(aggregate_val_2['qty_done'], 6)
  5059. self.assertEqual(aggregate_val_3['qty_ordered'], 10)
  5060. self.assertEqual(aggregate_val_3['qty_done'], 7)
  5061. # Delivers a part of the second BO's qty. but doesn't create a backorder this time.
  5062. second_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product3).qty_done = 3
  5063. backorder_wizard_dict = second_backorder.button_validate()
  5064. backorder_wizard_form = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context']))
  5065. backorder_wizard_form.save().process_cancel_backorder()
  5066. # Checks again the values for the original delivery.
  5067. aggregate_values = delivery.move_line_ids._get_aggregated_product_quantities()
  5068. self.assertEqual(len(aggregate_values), 2)
  5069. sml1 = delivery.move_line_ids.filtered(lambda ml: ml.product_id == self.product)
  5070. sml2 = delivery.move_line_ids.filtered(lambda ml: ml.product_id == product2)
  5071. aggregate_val_1 = aggregate_values[f'{self.product.id}_{self.product.name}__{sml1.product_uom_id.id}']
  5072. aggregate_val_2 = aggregate_values[f'{product2.id}_{product2.name}__{sml2.product_uom_id.id}']
  5073. self.assertEqual(aggregate_val_1['qty_ordered'], 10)
  5074. self.assertEqual(aggregate_val_1['qty_done'], 6)
  5075. self.assertEqual(aggregate_val_2['qty_ordered'], 10)
  5076. self.assertEqual(aggregate_val_2['qty_done'], 2)
  5077. # Checks again the values for the first back order.
  5078. aggregate_values = first_backorder.move_line_ids._get_aggregated_product_quantities()
  5079. self.assertEqual(len(aggregate_values), 3)
  5080. sml1 = first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == self.product)
  5081. sml2 = first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product2)
  5082. sml3 = first_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product3)
  5083. aggregate_val_1 = aggregate_values[f'{self.product.id}_{self.product.name}__{sml1.product_uom_id.id}']
  5084. aggregate_val_2 = aggregate_values[f'{product2.id}_{product2.name}__{sml2.product_uom_id.id}']
  5085. aggregate_val_3 = aggregate_values[f'{product3.id}_{product3.name}__{sml3.product_uom_id.id}']
  5086. self.assertEqual(aggregate_val_1['qty_ordered'], 4)
  5087. self.assertEqual(aggregate_val_1['qty_done'], 4)
  5088. self.assertEqual(aggregate_val_2['qty_ordered'], 8)
  5089. self.assertEqual(aggregate_val_2['qty_done'], 6)
  5090. self.assertEqual(aggregate_val_3['qty_ordered'], 10)
  5091. self.assertEqual(aggregate_val_3['qty_done'], 7)
  5092. # Checks the values for the second back order.
  5093. aggregate_values = second_backorder.move_line_ids._get_aggregated_product_quantities()
  5094. self.assertEqual(len(aggregate_values), 2)
  5095. sml1 = second_backorder.move_line_ids.filtered(lambda ml: ml.product_id == product3)
  5096. sm2 = second_backorder.move_ids.filtered(lambda ml: ml.product_id == product2)
  5097. aggregate_val_1 = aggregate_values[f'{product3.id}_{product3.name}__{sml1.product_uom_id.id}']
  5098. aggregate_val_2 = aggregate_values[f'{product2.id}_{product2.name}__{sm2.product_uom.id}']
  5099. self.assertEqual(aggregate_val_1['qty_ordered'], 3)
  5100. self.assertEqual(aggregate_val_1['qty_done'], 3)
  5101. self.assertEqual(aggregate_val_2['qty_ordered'], 2)
  5102. self.assertEqual(aggregate_val_2['qty_done'], 0)
  5103. def test_move_line_aggregated_product_quantities_duplicate_stock_move(self):
  5104. """ Test the `stock.move.line` method `_get_aggregated_product_quantities`,
  5105. which returns data used to print delivery slips, with two stock moves of the same product
  5106. """
  5107. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 25)
  5108. picking = self.env['stock.picking'].create({
  5109. 'location_id': self.stock_location.id,
  5110. 'location_dest_id': self.customer_location.id,
  5111. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5112. })
  5113. move1 = self.env['stock.move'].create({
  5114. 'name': 'test_transit_1',
  5115. 'location_id': self.stock_location.id,
  5116. 'location_dest_id': self.customer_location.id,
  5117. 'product_id': self.product.id,
  5118. 'product_uom': self.uom_unit.id,
  5119. 'product_uom_qty': 10.0,
  5120. 'picking_id': picking.id,
  5121. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5122. })
  5123. move2 = self.env['stock.move'].create({
  5124. 'name': 'test_transit_2',
  5125. 'location_id': self.stock_location.id,
  5126. 'location_dest_id': self.customer_location.id,
  5127. 'product_id': self.product.id,
  5128. 'product_uom': self.uom_unit.id,
  5129. 'product_uom_qty': 5.0,
  5130. 'picking_id': picking.id,
  5131. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5132. })
  5133. self.env['stock.move.line'].create({
  5134. 'move_id': move1.id,
  5135. 'product_id': move1.product_id.id,
  5136. 'qty_done': 10,
  5137. 'product_uom_id': move1.product_uom.id,
  5138. 'picking_id': picking.id,
  5139. 'location_id': move1.location_id.id,
  5140. 'location_dest_id': move1.location_dest_id.id,
  5141. })
  5142. self.env['stock.move.line'].create({
  5143. 'move_id': move2.id,
  5144. 'product_id': move2.product_id.id,
  5145. 'qty_done': 5,
  5146. 'product_uom_id': move2.product_uom.id,
  5147. 'picking_id': picking.id,
  5148. 'location_id': move2.location_id.id,
  5149. 'location_dest_id': move2.location_dest_id.id,
  5150. })
  5151. aggregate_values = picking.move_line_ids._get_aggregated_product_quantities()
  5152. aggregated_val = aggregate_values[f'{self.product.id}_{self.product.name}__{self.product.uom_id.id}']
  5153. self.assertEqual(aggregated_val['qty_ordered'], 15)
  5154. picking.button_validate()
  5155. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 10)
  5156. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 15)
  5157. def test_move_line_aggregated_product_quantities_two_packages(self):
  5158. """ Test the `stock.move.line` method `_get_aggregated_product_quantities`,
  5159. which returns data used to print delivery slips, with two packages
  5160. """
  5161. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 25)
  5162. picking = self.env['stock.picking'].create({
  5163. 'location_id': self.stock_location.id,
  5164. 'location_dest_id': self.customer_location.id,
  5165. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5166. })
  5167. move1 = self.env['stock.move'].create({
  5168. 'name': 'test_transit_1',
  5169. 'location_id': self.stock_location.id,
  5170. 'location_dest_id': self.customer_location.id,
  5171. 'product_id': self.product.id,
  5172. 'product_uom': self.uom_unit.id,
  5173. 'product_uom_qty': 15.0,
  5174. 'picking_id': picking.id,
  5175. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5176. })
  5177. picking.action_confirm()
  5178. picking.action_assign()
  5179. move1.quantity_done = 5
  5180. picking.action_put_in_pack() # Create a first package
  5181. self.assertEqual(len(picking.move_line_ids), 2)
  5182. unpacked_ml = picking.move_line_ids.filtered(lambda ml: not ml.result_package_id)
  5183. self.assertEqual(unpacked_ml.reserved_qty, 10)
  5184. unpacked_ml.qty_done = 10
  5185. picking.action_put_in_pack() # Create a second package
  5186. self.assertEqual(len(picking.move_line_ids), 2)
  5187. picking.button_validate()
  5188. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 10)
  5189. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 15)
  5190. aggregate_values1 = picking.move_line_ids[0]._get_aggregated_product_quantities(strict=True)
  5191. aggregated_val = aggregate_values1[f'{self.product.id}_{self.product.name}__{self.product.uom_id.id}']
  5192. self.assertEqual(aggregated_val['qty_ordered'], 10)
  5193. aggregate_values2 = picking.move_line_ids[1]._get_aggregated_product_quantities(strict=True)
  5194. aggregated_val = aggregate_values2[f'{self.product.id}_{self.product.name}__{self.product.uom_id.id}']
  5195. self.assertEqual(aggregated_val['qty_ordered'], 5)
  5196. def test_move_line_aggregated_product_quantities_incomplete_package(self):
  5197. """ Test the `stock.move.line` method `_get_aggregated_product_quantities`,
  5198. which returns data used to print delivery slips, with an incomplete order put in packages
  5199. """
  5200. self.env['stock.quant']._update_available_quantity(self.product, self.stock_location, 25)
  5201. picking = self.env['stock.picking'].create({
  5202. 'location_id': self.stock_location.id,
  5203. 'location_dest_id': self.customer_location.id,
  5204. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5205. })
  5206. move1 = self.env['stock.move'].create({
  5207. 'name': 'test_transit_1',
  5208. 'location_id': self.stock_location.id,
  5209. 'location_dest_id': self.customer_location.id,
  5210. 'product_id': self.product.id,
  5211. 'product_uom': self.uom_unit.id,
  5212. 'product_uom_qty': 15.0,
  5213. 'picking_id': picking.id,
  5214. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5215. })
  5216. move1.quantity_done = 5
  5217. picking.action_put_in_pack() # Create a package
  5218. delivery_form = Form(picking)
  5219. delivery = delivery_form.save()
  5220. delivery.action_confirm()
  5221. backorder_wizard_dict = delivery.button_validate()
  5222. backorder_wizard_form = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context']))
  5223. backorder_wizard_form.save().process_cancel_backorder() # Don't create a backorder
  5224. aggregate_values = picking.move_line_ids._get_aggregated_product_quantities()
  5225. aggregated_val = aggregate_values[f'{self.product.id}_{self.product.name}__{self.product.uom_id.id}']
  5226. self.assertEqual(aggregated_val['qty_ordered'], 15)
  5227. self.assertEqual(aggregated_val['qty_done'], 5)
  5228. aggregate_values = picking.move_line_ids._get_aggregated_product_quantities(strict=True)
  5229. aggregated_val = aggregate_values[f'{self.product.id}_{self.product.name}__{self.product.uom_id.id}']
  5230. self.assertEqual(aggregated_val['qty_ordered'], 5)
  5231. self.assertEqual(aggregated_val['qty_done'], 5)
  5232. aggregate_values = picking.move_line_ids._get_aggregated_product_quantities(except_package=True)
  5233. aggregated_val = aggregate_values[f'{self.product.id}_{self.product.name}__{self.product.uom_id.id}']
  5234. self.assertEqual(aggregated_val['qty_ordered'], 10)
  5235. self.assertEqual(aggregated_val['qty_done'], False)
  5236. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.stock_location), 20)
  5237. self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product, self.customer_location), 5)
  5238. def test_move_sn_warning(self):
  5239. """ Check that warnings pop up when duplicate SNs added or when SN isn't in
  5240. expected location.
  5241. Two cases covered:
  5242. - Check for dupes when assigning serial number to a stock move
  5243. - Check for dupes when assigning serial number to a stock move line
  5244. """
  5245. lot1 = self.env['stock.lot'].create({
  5246. 'name': 'serial1',
  5247. 'product_id': self.product_serial.id,
  5248. 'company_id': self.env.company.id,
  5249. })
  5250. self.env['stock.quant']._update_available_quantity(self.product_serial, self.pack_location, 1, lot1)
  5251. move = self.env['stock.move'].create({
  5252. 'name': 'test sn',
  5253. 'location_id': self.supplier_location.id,
  5254. 'location_dest_id': self.stock_location.id,
  5255. 'product_id': self.product_serial.id,
  5256. 'product_uom': self.uom_unit.id,
  5257. 'product_uom_qty': 1.0,
  5258. })
  5259. move_line = self.env['stock.move.line'].create({
  5260. 'move_id': move.id,
  5261. 'product_id': move.product_id.id,
  5262. 'qty_done': 1,
  5263. 'product_uom_id': move.product_uom.id,
  5264. 'location_id': move.location_id.id,
  5265. 'location_dest_id': move.location_dest_id.id,
  5266. 'lot_name': lot1.name,
  5267. })
  5268. warning = False
  5269. warning = move_line._onchange_serial_number()
  5270. self.assertTrue(warning, 'Reuse of existing serial number (name) not detected')
  5271. self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
  5272. move_line.write({
  5273. 'lot_name': False,
  5274. 'lot_id': lot1.id
  5275. })
  5276. warning = False
  5277. warning = move_line._onchange_serial_number()
  5278. self.assertTrue(warning, 'Reuse of existing serial number (record) not detected')
  5279. self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
  5280. self.assertEqual(move_line.location_id, self.pack_location, 'Location was not auto-corrected')
  5281. move.lot_ids = lot1
  5282. warning = False
  5283. warning = move._onchange_lot_ids()
  5284. self.assertTrue(warning, 'Reuse of existing serial number (record) not detected')
  5285. self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
  5286. def test_forecast_availability(self):
  5287. """ Make an outgoing picking in dozens for a product stored in units.
  5288. Check that reserved_availabity is expressed in move uom and forecast_availability is in product base uom
  5289. """
  5290. # create product
  5291. product = self.env['product.product'].create({
  5292. 'name': 'Product In Units',
  5293. 'type': 'product',
  5294. 'categ_id': self.env.ref('product.product_category_all').id,
  5295. })
  5296. # make some stock
  5297. self.env['stock.quant']._update_available_quantity(product, self.stock_location, 36.0)
  5298. # create picking
  5299. picking_out = self.env['stock.picking'].create({
  5300. 'picking_type_id': self.env.ref('stock.picking_type_out').id,
  5301. 'location_id': self.stock_location.id,
  5302. 'location_dest_id': self.customer_location.id})
  5303. move = self.env['stock.move'].create({
  5304. 'name': product.name,
  5305. 'product_id': product.id,
  5306. 'product_uom': self.uom_dozen.id,
  5307. 'product_uom_qty': 2.0,
  5308. 'picking_id': picking_out.id,
  5309. 'location_id': self.stock_location.id,
  5310. 'location_dest_id': self.customer_location.id})
  5311. # confirm
  5312. picking_out.action_confirm()
  5313. # check availability
  5314. picking_out.action_assign()
  5315. # check reserved_availabity expressed in move uom
  5316. self.assertEqual(move.reserved_availability, 2)
  5317. # check forecast_availability expressed in product base uom
  5318. self.assertEqual(move.forecast_availability, 24)
  5319. def test_SML_location_selection(self):
  5320. """
  5321. Suppose the setting 'Storage Categories' disabled and the option 'Show Detailed Operations'
  5322. for operation 'Internal Transfer' enabled.
  5323. A user creates an internal transfer from F to T, confirms it then adds a SML and selects
  5324. another destination location L (with L a child of T). When the user completes the field
  5325. `qty_done`, the onchange should n't change the destination location L
  5326. """
  5327. self.env.user.write({'groups_id': [(3, self.env.ref('stock.group_stock_storage_categories').id)]})
  5328. internal_transfer = self.env.ref('stock.picking_type_internal')
  5329. internal_transfer.show_operations = True
  5330. picking = self.env['stock.picking'].create({
  5331. 'picking_type_id': internal_transfer.id,
  5332. 'location_id': self.stock_location.id,
  5333. 'location_dest_id': self.customer_location.id,
  5334. })
  5335. self.env['stock.move'].create({
  5336. 'name': self.product_consu.name,
  5337. 'product_id': self.product_consu.id,
  5338. 'product_uom': self.uom_unit.id,
  5339. 'product_uom_qty': 2.0,
  5340. 'picking_id': picking.id,
  5341. 'location_id': picking.location_id.id,
  5342. 'location_dest_id': picking.location_dest_id.id,
  5343. })
  5344. picking.action_confirm()
  5345. with Form(picking) as form:
  5346. with form.move_line_ids_without_package.edit(0) as line:
  5347. line.location_dest_id = self.stock_location.child_ids[0]
  5348. line.qty_done = 1
  5349. self.assertEqual(picking.move_line_ids_without_package.location_dest_id, self.stock_location.child_ids[0])
  5350. def test_inter_wh_and_forecast_availability(self):
  5351. dest_wh = self.env['stock.warehouse'].create({
  5352. 'name': 'Second Warehouse',
  5353. 'code': 'WH02',
  5354. })
  5355. move = self.env['stock.move'].create({
  5356. 'name': 'test_interwh',
  5357. 'location_id': self.stock_location.id,
  5358. 'location_dest_id': dest_wh.lot_stock_id.id,
  5359. 'product_id': self.product.id,
  5360. 'product_uom': self.uom_unit.id,
  5361. 'product_uom_qty': 1.0,
  5362. })
  5363. self.assertEqual(move.forecast_availability, -1)
  5364. move._action_confirm()
  5365. self.assertEqual(move.forecast_availability, -1)
  5366. def test_move_compute_uom(self):
  5367. move = self.env['stock.move'].create({
  5368. 'name': 'foo',
  5369. 'product_id': self.product.id,
  5370. 'location_id': self.stock_location.id,
  5371. 'location_dest_id': self.customer_location.id,
  5372. 'move_line_ids': [(0, 0, {})]
  5373. })
  5374. self.assertEqual(move.product_uom, self.product.uom_id)
  5375. self.assertEqual(move.move_line_ids.product_uom_id, self.product.uom_id)
  5376. uom_kg = self.env.ref('uom.product_uom_kgm')
  5377. product1 = self.env['product.product'].create({
  5378. 'name': 'product1',
  5379. 'type': 'product',
  5380. 'uom_id': uom_kg.id,
  5381. 'uom_po_id': uom_kg.id
  5382. })
  5383. move.product_id = product1
  5384. self.assertEqual(move.product_uom, product1.uom_id)
  5385. def test_move_line_compute_locations(self):
  5386. stock_location = self.env['stock.location'].create({
  5387. 'name': 'test-stock',
  5388. 'usage': 'internal',
  5389. })
  5390. shelf_location = self.env['stock.location'].create({
  5391. 'name': 'shelf1',
  5392. 'usage': 'internal',
  5393. 'location_id': stock_location.id,
  5394. })
  5395. move = self.env['stock.move'].create({
  5396. 'name': 'foo',
  5397. 'product_id': self.product.id,
  5398. 'location_id': stock_location.id,
  5399. 'location_dest_id': shelf_location.id,
  5400. 'move_line_ids': [(0, 0, {})]
  5401. })
  5402. self.assertEqual(move.move_line_ids.location_id, stock_location)
  5403. self.assertEqual(move.move_line_ids.location_dest_id, shelf_location)
  5404. # directly created mls should default to picking's src/dest locations
  5405. internal_transfer = self.env.ref('stock.picking_type_internal')
  5406. picking = self.env['stock.picking'].create({
  5407. 'picking_type_id': internal_transfer.id,
  5408. 'location_id': stock_location.id,
  5409. 'location_dest_id': shelf_location.id,
  5410. 'move_line_nosuggest_ids': [Command.create({
  5411. 'product_id': self.product.id,
  5412. 'qty_done': 1.0
  5413. })]
  5414. })
  5415. self.assertEqual(picking.move_line_ids.location_id.id, stock_location.id)
  5416. self.assertEqual(picking.move_line_ids.location_dest_id.id, shelf_location.id)
  5417. def test_receive_more_and_in_child_location(self):
  5418. """
  5419. Ensure that, when receiving more than expected, and when the destination
  5420. location of the SML is different from the SM one, the SM validation will
  5421. not change the destination location of the SML
  5422. """
  5423. move = self.env['stock.move'].create({
  5424. 'name': self.product.name,
  5425. 'location_id': self.supplier_location.id,
  5426. 'location_dest_id': self.stock_location.id,
  5427. 'product_id': self.product.id,
  5428. 'product_uom': self.uom_unit.id,
  5429. 'product_uom_qty': 1.0,
  5430. })
  5431. move._action_confirm()
  5432. move.move_line_ids.write({
  5433. 'location_dest_id': self.stock_location.child_ids[0].id,
  5434. 'qty_done': 3,
  5435. })
  5436. move._action_done()
  5437. self.assertEqual(move.move_line_ids.qty_done, 3)
  5438. self.assertEqual(move.move_line_ids.location_dest_id, self.stock_location.child_ids[0])
  5439. def test_serial_tracking(self):
  5440. """
  5441. Since updating the move's `lot_ids` field for product tracked by serial numbers will
  5442. also updates the move's `quantity_done`, this test checks the move's move lines will be
  5443. correclty updated and consequently its picking can be validated.
  5444. """
  5445. sn = self.env['stock.lot'].create({
  5446. 'name': 'test_lot_001',
  5447. 'product_id': self.product_serial.id,
  5448. 'company_id': self.env.company.id,
  5449. })
  5450. picking_form = Form(self.env['stock.picking'])
  5451. picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
  5452. with picking_form.move_ids_without_package.new() as move:
  5453. move.product_id = self.product_serial
  5454. move.product_uom_qty = 1
  5455. receipt = picking_form.save()
  5456. receipt.action_confirm()
  5457. receipt_form = Form(receipt)
  5458. with receipt_form.move_ids_without_package.edit(0) as move:
  5459. move.lot_ids.add(sn)
  5460. receipt = receipt_form.save()
  5461. receipt.button_validate()
  5462. self.assertEqual(receipt.state, 'done')
  5463. self.assertEqual(len(receipt.move_line_ids), 1)
  5464. self.assertEqual(receipt.move_line_ids.qty_done, 1)