models.py 274 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. """
  4. web.user.models
  5. ~~~~~~~~~
  6. """
  7. import copy
  8. import datetime
  9. import logging
  10. import random
  11. import string
  12. import threading
  13. import time
  14. import uuid
  15. from collections import Counter, defaultdict
  16. from arrow import Arrow
  17. from bson.objectid import ObjectId
  18. from dateutil import relativedelta
  19. from django.conf import settings
  20. from django.utils.functional import cached_property
  21. from mongoengine import Q
  22. from mongoengine.document import EmbeddedDocument
  23. from mongoengine.errors import NotUniqueError, DoesNotExist
  24. from mongoengine.fields import (BooleanField, StringField, IntField, EmbeddedDocumentListField,
  25. DateTimeField, PointField, DictField, ObjectIdField, ListField, EmbeddedDocumentField,
  26. MapField, GenericLazyReferenceField, LazyReferenceField)
  27. from pymongo.errors import DuplicateKeyError
  28. from pymongo.results import UpdateResult
  29. from typing import Union, Dict, AnyStr, Optional, TYPE_CHECKING, List, Tuple
  30. from apilib.monetary import RMB, VirtualCoin, Ratio, Percent, Money
  31. from apilib.quantity import Quantity
  32. from apilib.systypes import IterConstant
  33. from apilib.utils import flatten
  34. from apilib.utils_datetime import generate_timestamp_ex, today_format_str, to_datetime, get_tomorrow_zero_time
  35. from apilib.utils_mongo import BulkHandlerEx
  36. from apilib.utils_string import get_random_str
  37. from apps import serviceCache
  38. from apps.web.agent.models import Agent, MoniApp
  39. from apps.web.common.models import OrderRecordBase
  40. from apps.web.common.transaction import OrderNoMaker, OrderMainType, UserPaySubType, UserConsumeSubType, RefundSubType
  41. from apps.web.common.validation import CARD_NO_RE
  42. from apps.web.constant import (Const,
  43. DEALER_CONSUMPTION_AGG_KIND, GLOSSARY_TRANSLATION,
  44. USER_RECHARGE_TYPE,
  45. RECHARGE_CARD_TYPE,
  46. AppPlatformType, APP_PLATFORM_TYPE_TRANSLATION,
  47. RechargeRecordVia, RECHARGE_RECORD_VIA_TRANSLATION, ErrorCode,
  48. DEVICE_INCOME_STRATEGY, support_policy_device, support_policy_weifule,
  49. PARTITION_ROLE)
  50. from apps.web.core import PayAppType, ROLE
  51. from apps.web.core.bridge import WechatClientProxy
  52. from apps.web.core.db import Searchable, MonetaryField, VirtualCoinField, RoleBaseDocument, BooleanIntField
  53. from apps.web.core.exceptions import InsufficientFundsError, ServiceException, ImproperlyOperatedBalance
  54. from apps.web.core.models import BoundOpenInfo
  55. from apps.web.core.models import WechatAuthApp
  56. from apps.web.dealer.models import Dealer, VirtualCard, DealerDict
  57. from apps.web.device.models import Group, Device, DeviceType
  58. from apps.web.exceptions import UserServerException, PostPayOrderError
  59. from apps.web.report.ledger import AgentLedgerFirst, PartnerLedgerFirst
  60. from apps.web.report.utils import record_consumption_stats
  61. from apps.web.user.constant2 import StartDeviceType, PackageCategory, CONSUME_ORDER_PAY_TIMEOUT, ConsumeOrderServiceItem, UserBalanceChangeCategory
  62. from apps.web.utils import concat_front_end_url, concat_user_login_entry_url, concat_user_center_entry_url, set_or_incr_cache, concat_count_down_page_url
  63. from library.idgen import IDGenService
  64. from apps.web.core.payment import PaymentGateway
  65. logger = logging.getLogger(__name__)
  66. if TYPE_CHECKING:
  67. from apps.web.device.models import FeedBack
  68. from apps.web.device.models import DeviceDict, GroupDict
  69. from apps.web.common.models import CapitalUser
  70. from apps.web.dealer.models import MonthlyPackageTemp
  71. from mongoengine.queryset import QuerySet
  72. from apps.web.dealer.models import DealerRechargeRecord
  73. from apps.web.user.utils2 import StartParamContext, check_consume_order_timeout
  74. class MyUserAuthBackend(object):
  75. """
  76. 部分接口需要用户鉴权,由于目前用户体系尚未完整建立,在这里首先实现MVP
  77. """
  78. # noinspection PyUnusedLocal
  79. def authenticate(self, **kwargs):
  80. return True
  81. def get_user(self, user_id):
  82. return self.user_document.objects.with_id(user_id)
  83. @property
  84. def user_document(self):
  85. self._user_doc = MyUser
  86. return self._user_doc
  87. class EndUserLocation(EmbeddedDocument):
  88. logicalCode = StringField(verbose_name = '')
  89. # point :: { "type" : "Point" , "coordinates" : [longitude, latitude]}
  90. point = PointField(default = None, verbose_name = u'用户的经纬度')
  91. type = StringField(verbose_name = u'经纬度类型')
  92. createdTime = DateTimeField(default = datetime.datetime.now)
  93. @property
  94. def coordinates(self):
  95. if not self.point:
  96. return ()
  97. else:
  98. return tuple(self.point['coordinates'])
  99. def __repr__(self):
  100. return '<EndUserLocation (logicalCode=%s, point=(lng=%s, lat=%s), type=%s)>' \
  101. % (self.logicalCode,
  102. self.point['coordinates'][0],
  103. self.point['coordinates'][1],
  104. self.type)
  105. class UserMoney(EmbeddedDocument):
  106. settled = MonetaryField(verbose_name = "已经分账给经销商的金额", default = RMB('0.00')) # type: RMB
  107. unsettled = MonetaryField(verbose_name = "未分账给经销商的金额", default = RMB('0.00')) # type: RMB
  108. total_recharged = MonetaryField(default = RMB('0.00')) # type: RMB
  109. total_consumed = MonetaryField(default = RMB('0.00')) # type: RMB
  110. def __repr__(self):
  111. return '<UserMoney settled=%s, unsettled=%s, total_recharged=%s, total_consumed=%s>' \
  112. % (self.settled, self.unsettled, self.total_recharged, self.total_consumed)
  113. def to_dict(self):
  114. return {
  115. 'settled': self.settled,
  116. 'unsettled': self.unsettled,
  117. 'total_recharged': self.total_recharged,
  118. 'total_consumed': self.total_consumed
  119. }
  120. class UserCoin(EmbeddedDocument):
  121. balance = VirtualCoinField(default = VirtualCoin('0.00'))
  122. total_recharged = VirtualCoinField(default = VirtualCoin('0.00'))
  123. total_consumed = VirtualCoinField(default = VirtualCoin('0.00'))
  124. def __repr__(self):
  125. return '<UserCoin balance=%s total_recharged=%s total_consumed=%s>' \
  126. % (self.balance, self.total_recharged, self.total_consumed)
  127. def to_dict(self):
  128. return {
  129. 'balance': self.balance,
  130. 'total_recharged': self.total_recharged,
  131. 'total_consumed': self.total_consumed
  132. }
  133. class UniqueUser(Searchable):
  134. openId = StringField(verbose_name='第三方用户ID(微信的OPENID,支付宝的UID)', null=False, unique=True)
  135. userId = StringField(verbose_name='用户平台UID')
  136. phone = StringField(verbose_name='电话号码', default="")
  137. meta = {
  138. 'collection': 'unique_user',
  139. 'db_alias': 'default',
  140. 'indexes': [
  141. {'fields': ['userId'], 'unique': True, 'sparse': True}
  142. ]
  143. }
  144. @classmethod
  145. def get_or_create(cls, openId): # type:(str) -> UniqueUser
  146. """
  147. 创建或者是获取用户
  148. """
  149. user = cls.objects(openId = openId).first()
  150. if user:
  151. return user
  152. try:
  153. return cls(openId = openId).save()
  154. except DuplicateKeyError:
  155. return cls.objects(openId = openId).first()
  156. def update_phone(self, phone): # type:(str) -> bool
  157. """
  158. 更新电话号码
  159. """
  160. return bool(self.update(phone=phone))
  161. class MyUser(RoleBaseDocument):
  162. """
  163. `EndUser`
  164. 终端用户模型
  165. """
  166. sex = IntField(verbose_name=u"性别", default=Const.USER_SEX.UNKNOWN)
  167. phoneOS = StringField(verbose_name=u"终端操作系统", default="")
  168. city = StringField(verbose_name=u"城市", default="")
  169. province = StringField(verbose_name=u"省份", default="")
  170. country = StringField(verbose_name=u"国家", default="")
  171. avatar = StringField(verbose_name=u"头像地址", default="")
  172. nickname = StringField(verbose_name=u"名称", max_length=255, default="")
  173. groupId = StringField(verbose_name=u"地址编号", default="")
  174. gateway = StringField(verbose_name=u"来自支付宝或微信", default='wechat')
  175. chargeBalance = MonetaryField(verbose_name=u"余额", default=RMB('0'))
  176. bestowBalance = MonetaryField(verbose_name=u"赠送余额", default=RMB('0'))
  177. total_recharged = MonetaryField(verbose_name="累计充值", default=RMB('0'))
  178. total_bestow = MonetaryField(verbose_name=u"累计赠送", default=RMB('0'))
  179. total_consumed = VirtualCoinField(verbose_name="累计消费", default=RMB('0'))
  180. phoneNumber = StringField(verbose_name=u"用户手机号码", default="")
  181. authAppId = StringField(verbose_name=u"鉴权appid", default="")
  182. openId = StringField(verbose_name=u"openID|支付宝buyerID", default="")
  183. unionId = StringField(verbose_name=u"统一用户ID", default="")
  184. managerialAppId = StringField(verbose_name=u"管理公众号AppId", default="")
  185. managerialOpenId = StringField(verbose_name=u"管理openId", default="")
  186. payAppId = StringField(verbose_name=u"最近登录微信授权appid", default="")
  187. payOpenId = StringField(verbose_name=u"openId", default="")
  188. payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo))
  189. extra = DictField(verbose_name=u"多余字段", default={})
  190. dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'添加进来的时间')
  191. last_login = DateTimeField(default=datetime.datetime.now, verbose_name=u'最近登录时间')
  192. lastLoginUserAgent = StringField(verbose_name=u"最后一次登录的userAgent", default="")
  193. locations = EmbeddedDocumentListField(document_type=EndUserLocation)
  194. agentId = StringField(verbose_name=u'当前用户绑定的直接上级代理商', default='')
  195. productAgentId = StringField(verbose_name=u'当前用户绑定的平台代理商', default='')
  196. promo = DictField(verbose_name=u'活动相关信息', default={})
  197. favoriteDeviceList = ListField(verbose_name=u'收藏的宝贝', default=[])
  198. smsVendor = StringField(verbose_name=u'sms提供商', default='')
  199. ongoingList = ListField(field=DictField(), verbose=u'冻结的金额')
  200. blacklistConfig = DictField(verbose_name=u'黑名单相关设置', default={})
  201. meta = {
  202. "collection": "MyUser2",
  203. "db_alias": "default",
  204. "indexes": [{
  205. 'fields': ['$openId', '$nickname'],
  206. 'weights': {'openId': 10, 'nickname': 2}
  207. },
  208. 'openId',
  209. 'nickname',
  210. 'dateTimeAdded',
  211. # 'groupId'
  212. ],
  213. # "shard_key": ('openId',)
  214. }
  215. search_fields = ('openId', 'nickname')
  216. _AGENT_GROUP_ID_PREFIX = 'agent_'
  217. __mgr_cache = serviceCache
  218. def __str__(self):
  219. return '{}<id={} openId={} groupId={}>'.format(self.__class__.__name__, str(self.id), self.openId, self.groupId)
  220. @property
  221. def balance(self):
  222. return self.chargeBalance + self.bestowBalance
  223. def pay(self, payment): # type:(PaymentInfo) -> PaymentInfo
  224. if not payment:
  225. return
  226. if not payment.deduct_list:
  227. return
  228. # 虽然是循环扣除 但是一般情况下只会扣除1次
  229. bulker = BulkHandlerEx(self.__class__.get_collection()) # type: BulkHandlerEx
  230. chargeBalanceField = self.__class__.chargeBalance.name
  231. bestowBalanceField = self.__class__.bestowBalance.name
  232. for deduct in payment.deduct_list:
  233. query = {
  234. '_id': ObjectId(deduct['id']),
  235. }
  236. update = {
  237. '$inc': {
  238. chargeBalanceField: (-RMB(deduct[chargeBalanceField])).mongo_amount,
  239. bestowBalanceField: (-RMB(deduct[bestowBalanceField])).mongo_amount
  240. }
  241. }
  242. bulker.update(query, update)
  243. result = bulker.execute()
  244. if result['success'] == 0 or len(result['info']['writeErrors']) != 0:
  245. logger.error("[user pay] pay error, result = {}".format(result))
  246. else:
  247. # 添加支付时间并返回
  248. payment.time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  249. return payment
  250. def account_consume(self, order): # type:(ConsumeRecord) -> None
  251. payment = order.payment
  252. # 获取支付的钱
  253. consumeAmount = payment.actualAmount
  254. consumeBestowAmount = payment.totalAmount - consumeAmount
  255. # 获取平台余额
  256. charge, bestow, _, __ = self.filter_my_balance()
  257. UserBalanceLog.consume(
  258. order.user,
  259. afterAmount=charge,
  260. afterBestowAmount=bestow,
  261. consumeAmount=consumeAmount,
  262. consumeBestowAmount=consumeBestowAmount,
  263. order=order
  264. )
  265. def account_refund(self, order): # type:(ConsumeRecord) -> None
  266. refund = order.refund
  267. # 获取支付的钱
  268. refundAmount = refund.actualAmount
  269. refundBestowAmount = refund.totalAmount - refundAmount
  270. # 获取平台余额
  271. charge, bestow, _, __ = self.filter_my_balance()
  272. UserBalanceLog.refund(
  273. order.user,
  274. afterAmount=charge,
  275. afterBestowAmount=bestow,
  276. refundAmount=refundAmount,
  277. refundBestowAmount=refundBestowAmount,
  278. order=order
  279. )
  280. def account_recharge(self, order): # type:(RechargeRecord) -> None
  281. # 获取支付的钱
  282. chargeAmount = order.chargeAmount
  283. bestowAmount = order.bestowAmount
  284. # 获取平台余额
  285. charge, bestow, _, __ = self.filter_my_balance()
  286. UserBalanceLog.recharge(
  287. order.user,
  288. afterAmount=charge,
  289. afterBestowAmount=bestow,
  290. chargeAmount=chargeAmount,
  291. chargeBestowAmount=bestowAmount,
  292. order=order
  293. )
  294. def is_authenticated(self):
  295. if not self.authAppId:
  296. return False
  297. return True
  298. def get_promo_info(self, key):
  299. return self.promo.get(key)
  300. def set_promo_info(self, key, value):
  301. promo = self.promo
  302. promo[key] = value
  303. return self.update(promo = promo)
  304. @property
  305. def inhouse_promo_openId(self):
  306. return self.get_promo_info("inhouse_openId")
  307. def set_inhouse_promo_openId(self, openId):
  308. return self.set_promo_info(key = "inhouse_openId", value = openId)
  309. @property
  310. def feature_keys(self):
  311. return ['phoneOS', 'sex', 'gateway']
  312. @property
  313. def feature_map(self):
  314. return {'phoneOS': self.phoneOS, 'sex': self.sex_in_en, 'gateway': self.gateway}
  315. @property
  316. def sex_in_en(self):
  317. if self.is_female:
  318. return 'female'
  319. elif self.is_male:
  320. return 'male'
  321. else:
  322. return ''
  323. @property
  324. def is_female(self):
  325. return self.sex == Const.USER_SEX.FEMALE
  326. @property
  327. def is_male(self):
  328. return self.sex == Const.USER_SEX.MALE
  329. def get_bound_pay_openid(self, key):
  330. # type: (str)->str
  331. if self.gateway == AppPlatformType.ALIPAY:
  332. return self.openId
  333. else:
  334. pay_openid_map = self.payOpenIdMap # type: dict
  335. bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) # type: BoundOpenInfo
  336. return bound.openId
  337. def set_bound_pay_openid(self, key, **payload):
  338. # type: (str, Dict)->None
  339. self.payOpenIdMap[key] = BoundOpenInfo(**payload)
  340. @classmethod
  341. def _product_group_id(cls, agent_id):
  342. # type:(str)->str
  343. return '{}{}'.format(cls._AGENT_GROUP_ID_PREFIX, agent_id)
  344. @classmethod
  345. def _is_product_group_id(cls, group_id):
  346. # type: (str)->bool
  347. return group_id.startswith(cls._AGENT_GROUP_ID_PREFIX)
  348. def is_product_user(self):
  349. return not self.groupId or self.groupId.startswith(self._AGENT_GROUP_ID_PREFIX)
  350. @classmethod
  351. def get_product_users(cls, agent_id): # type: (str) -> QuerySet
  352. productGroupId = cls._product_group_id(agent_id)
  353. return cls.objects.filter(groupId=productGroupId)
  354. @classmethod
  355. def get_or_create(cls, app_platform_type, open_id, group_id=groupId.default, **kwargs):
  356. # type: (str, str, str, Dict)->MyUser
  357. logger.info('get_or_create cls = {}, app_platform_type = {}, open_id = {}, groupId = {}, kwargs = {}'.format(
  358. cls, app_platform_type, open_id, group_id, str(kwargs)))
  359. if app_platform_type in [AppPlatformType.ALIPAY, AppPlatformType.JD]:
  360. kwargs.pop('payOpenIdMap', None)
  361. if group_id == cls.groupId.default: # 平台个人中心登录
  362. format_group_id = cls._product_group_id(kwargs.get('productAgentId'))
  363. else:
  364. format_group_id = group_id
  365. user = cls.objects(openId = open_id, groupId = format_group_id).first() # type: MyUser
  366. if user is not None:
  367. need_update = False
  368. if 'payOpenIdMap' in kwargs:
  369. for key, value in kwargs['payOpenIdMap'].iteritems():
  370. if key in user.payOpenIdMap:
  371. continue
  372. user.set_bound_pay_openid(key = key, **(value.to_dict()))
  373. need_update = True
  374. if 'authAppId' in kwargs and kwargs['authAppId'] != user.authAppId:
  375. user.authAppId = kwargs['authAppId']
  376. need_update = True
  377. if 'agentId' in kwargs and kwargs['agentId'] != user.agentId:
  378. user.agentId = kwargs['agentId']
  379. need_update = True
  380. if 'productAgentId' in kwargs and kwargs['productAgentId'] != user.productAgentId:
  381. user.productAgentId = kwargs['productAgentId']
  382. need_update = True
  383. if 'avatar' in kwargs and kwargs['avatar'] != user.avatar:
  384. user.avatar = kwargs['avatar']
  385. need_update = True
  386. if need_update:
  387. user.save()
  388. return user
  389. else:
  390. kwargs.update({'gateway': app_platform_type})
  391. user = cls(openId = open_id, groupId = format_group_id, **kwargs)
  392. try:
  393. return user.save()
  394. except NotUniqueError:
  395. return cls.objects(openId = open_id, groupId = format_group_id).first()
  396. def group_pay(self, amount):
  397. # type: (VirtualCoin)->None
  398. """
  399. :param amount:
  400. :return:
  401. """
  402. #: 检查是否宇宙里未分配的余额和是否小于要付的钱
  403. valid_to_pay = bool(self.get_collection().count_documents(
  404. filter = {
  405. '_id': ObjectId(self.id),
  406. '$expr':
  407. {'$lt': [amount.mongo_amount, {'$sum': ["$universe.money.unsettled"]}]}
  408. }
  409. ))
  410. if valid_to_pay:
  411. raise NotImplementedError()
  412. def update_total_recharge(self, money):
  413. # type: (RMB)->int
  414. assert isinstance(money, RMB), 'money has to be RMB'
  415. updated = self.update(inc__total_recharged = money.amount)
  416. return updated
  417. def incr_balance(self, coins):
  418. # type: (VirtualCoin)->int
  419. assert isinstance(coins, VirtualCoin), 'coins had to be VirtualCoin'
  420. updated = self.update(inc__balance = coins.amount)
  421. return updated
  422. def recharge(self, money, bestowMoney): # type: (RMB, RMB)->MyUser
  423. """
  424. 给用户充值,同时记录累计充值数额
  425. :param money: 币额
  426. :param bestowMoney: 金额
  427. :return:
  428. """
  429. assert isinstance(money, RMB), 'coins had to be VirtualCoin'
  430. assert isinstance(bestowMoney, RMB), 'money has to be RMB'
  431. updated = self.update(
  432. inc__chargeBalance=money,
  433. inc__total_recharged=money,
  434. inc__bestowBalance=bestowMoney,
  435. inc__total_bestow=bestowMoney
  436. )
  437. if not updated:
  438. raise PostPayOrderError(u'余额和累计充值更新失败')
  439. return self.reload()
  440. @classmethod
  441. def deduct_balance(cls, deduct_list):
  442. # type:(dict)->None
  443. """
  444. 按照订单的扣款列表直接扣除用户相应balance
  445. :param deduct_list:
  446. :return:
  447. """
  448. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  449. for deduct in deduct_list:
  450. query = {
  451. '_id': ObjectId(deduct['id'])
  452. }
  453. update = {
  454. '$inc': {
  455. 'balance': (-VirtualCoin(deduct['coins'])).mongo_amount
  456. }
  457. }
  458. bulker.update(query, update)
  459. result = bulker.execute()
  460. logger.debug(result['info'])
  461. if result['success'] == 0:
  462. raise ServiceException({'result': 0, 'description': u"扣款失败(1001)"})
  463. else:
  464. if len(result['info']['writeErrors']) != 0:
  465. raise ServiceException({'result': 0, 'description': u"扣款失败(1002)"})
  466. @classmethod
  467. def freeze_balance(cls, transaction_id, payment): # type:(str, PaymentInfo) -> bool
  468. """
  469. 按照扣款列表冻结用户相应的金额
  470. :param transaction_id:
  471. :param payment:
  472. :return:
  473. """
  474. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  475. chargeBalanceField = cls.chargeBalance.name
  476. bestowBalanceField = cls.bestowBalance.name
  477. for deduct in payment.deduct_list:
  478. query = {
  479. '_id': ObjectId(deduct['id']),
  480. 'ongoingList.transaction_id': {
  481. '$ne': transaction_id
  482. }
  483. }
  484. update = {
  485. '$inc': {
  486. chargeBalanceField: (-RMB(deduct[chargeBalanceField])).mongo_amount,
  487. bestowBalanceField: (-RMB(deduct[bestowBalanceField])).mongo_amount
  488. },
  489. '$addToSet': {
  490. 'ongoingList': {
  491. 'transaction_id': transaction_id,
  492. chargeBalanceField: deduct[chargeBalanceField],
  493. bestowBalanceField: deduct[bestowBalanceField]
  494. }
  495. }
  496. }
  497. bulker.update(query, update)
  498. result = bulker.execute()
  499. logger.debug(result['info'])
  500. if result['success'] == 0:
  501. raise ServiceException({'result': 0, 'description': u"扣款失败(1001)"})
  502. else:
  503. if len(result['info']['writeErrors']) != 0:
  504. raise ServiceException({'result': 0, 'description': u"扣款失败(1002)"})
  505. else:
  506. return True
  507. @classmethod
  508. def clear_frozen_balance(cls, transaction_id, payment, refund): # type:(str, PaymentInfo, PaymentInfo)->bool
  509. """
  510. 清除冻结金额 实际上就是相当于退费
  511. 如果没有退费 也需要调用一次 目的是清理掉transactionId
  512. """
  513. try:
  514. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  515. chargeBalanceField = cls.chargeBalance.name
  516. bestowBalanceField = cls.bestowBalance.name
  517. # 将总消费情况 更新到第一个地址里面去
  518. first = True
  519. totalDeduct = payment.totalAmount
  520. totalRefund = refund.totalAmount
  521. for deduct in refund.deduct_list:
  522. query = {
  523. '_id': ObjectId(deduct['id']),
  524. 'ongoingList': {
  525. '$elemMatch': {
  526. 'transaction_id': transaction_id
  527. }
  528. }
  529. }
  530. if first:
  531. update = {
  532. '$inc': {
  533. chargeBalanceField: deduct[chargeBalanceField],
  534. bestowBalanceField: deduct[bestowBalanceField],
  535. cls.total_consumed.name: (totalDeduct - totalRefund).mongo_amount
  536. },
  537. '$pull': {
  538. 'ongoingList': {
  539. 'transaction_id': transaction_id
  540. }
  541. }
  542. }
  543. else:
  544. update = {
  545. '$inc': {
  546. chargeBalanceField: deduct[chargeBalanceField],
  547. bestowBalanceField: deduct[bestowBalanceField],
  548. },
  549. '$pull': {
  550. 'ongoingList': {
  551. 'transaction_id': transaction_id
  552. }
  553. }
  554. }
  555. first = False
  556. bulker.update(query, update)
  557. result = bulker.execute()
  558. logger.debug(result['info'])
  559. if result['success'] == 0:
  560. return False
  561. else:
  562. if len(result['info']['writeErrors']) != 0:
  563. return False
  564. else:
  565. return True
  566. except Exception as e:
  567. logger.exception(e)
  568. return False
  569. @classmethod
  570. def recover_frozen_balance(cls, transaction_id, deduct_list):
  571. # type:(str, list)->bool
  572. """
  573. 回滚冻结的余额
  574. """
  575. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  576. for deduct in deduct_list:
  577. query = {
  578. '_id': ObjectId(deduct['id']),
  579. 'ongoingList': {
  580. '$elemMatch': {'transaction_id': transaction_id}}}
  581. update = {
  582. '$inc': {
  583. 'balance': deduct['coins']
  584. },
  585. '$pull': {
  586. 'ongoingList': {
  587. 'transaction_id': transaction_id, 'frozenCoins': deduct['coins']
  588. }}
  589. }
  590. bulker.update(query, update)
  591. result = bulker.execute()
  592. if result['success'] == 0:
  593. logger.error(result['info'])
  594. return False
  595. else:
  596. if len(result['info']['writeErrors']) != 0:
  597. logger.error(result['info'])
  598. return False
  599. else:
  600. return True
  601. def to_dict(self, default_avatar = settings.DEFAULT_AVATAR_URL):
  602. avatar = self.avatar or default_avatar or Agent.get_agent(self.agentId).get("productLogo")
  603. return {
  604. 'avatar': avatar,
  605. 'nickname': self.nickname,
  606. 'openId': self.openId,
  607. 'groupId': self.groupId,
  608. 'sex': self.sex,
  609. 'balance': self.balance,
  610. 'total_recharged': self.total_recharged,
  611. 'total_consumed': self.total_consumed,
  612. }
  613. @property
  614. def product_users(self):
  615. return MyUser.objects.filter(
  616. __raw__ = {
  617. 'openId': self.openId,
  618. '$or': [
  619. {
  620. 'agentId': self.agentId
  621. },
  622. {
  623. 'productAgentId': self.productAgentId
  624. }
  625. ]
  626. })
  627. @property
  628. def total_balance(self):
  629. """
  630. 个人中心获取balance. 需要按照agentId过滤
  631. :return:
  632. """
  633. totalCharge, totalBestow, total, dataList = self.filter_my_balance()
  634. return totalCharge + totalBestow
  635. def filter_my_balance(self, currentGroup=None):
  636. """
  637. 个人中心获取balance列表. 需要按照agentId过滤
  638. """
  639. dataList = []
  640. total = 0
  641. totalCharge = RMB(0)
  642. totalBestow = RMB(0)
  643. chargeBalanceField = self.__class__.chargeBalance.name
  644. bestowBalanceField = self.__class__.bestowBalance.name
  645. # dealer_partition = defaultdict(lambda: {
  646. # chargeBalanceField: RMB(0),
  647. # bestowBalanceField: RMB(0)
  648. # })
  649. users = self.product_users
  650. for user in users: # type: MyUser
  651. groupId = user.groupId
  652. if user.is_product_user():
  653. logger.debug('groupId <{}> is agent group id.'.format(groupId))
  654. continue
  655. group = Group.get_group(groupId) # type: GroupDict
  656. if not group:
  657. logger.error('no group. id = %s' % groupId)
  658. continue
  659. dealer = Dealer.get_dealer(group['ownerId']) # type: DealerDict
  660. if dealer is None:
  661. logger.error('no dealer. id = %s' % group['ownerId'])
  662. continue
  663. # dealer_partition[dealer['id']][chargeBalanceField] += user.chargeBalance
  664. # dealer_partition[dealer['id']][bestowBalanceField] += user.bestowBalance
  665. # 过滤掉为0的情况
  666. if user.balance == RMB(0):
  667. continue
  668. else:
  669. total += 1
  670. totalCharge += user.chargeBalance
  671. totalBestow += user.bestowBalance
  672. address = group.address
  673. rv = {
  674. 'address': address,
  675. 'groupId': groupId,
  676. 'groupName': group.groupName,
  677. 'dealerId': group.ownerId,
  678. 'balance': {
  679. chargeBalanceField: user.chargeBalance,
  680. bestowBalanceField: user.bestowBalance
  681. },
  682. 'currency': False
  683. }
  684. if currentGroup and dealer.is_currency(currentGroup, group):
  685. rv['currency'] = True
  686. dataList.append(rv)
  687. return totalCharge, totalBestow, total, dataList
  688. def get_balance_in_dealer(self, dealer, group): # type: (Dealer, GroupDict)->Tuple[RMB, RMB, RMB]
  689. if self.groupId != group.groupId:
  690. payload = self.cloneable_user_info
  691. payload['agentId'] = dealer.agentId
  692. user = MyUser.get_or_create(
  693. app_platform_type = self.gateway, open_id = self.openId, group_id = group.groupId, **payload)
  694. else:
  695. user = self
  696. users = user.product_users
  697. overall, dealer_balance, currency_balance = RMB(0), RMB(0), RMB(0)
  698. dealer_group_ids = Group.get_group_ids_of_dealer(str(dealer.id)) # type: List[GroupDict]
  699. groups = Group.get_groups_by_group_ids(dealer_group_ids).values()
  700. share_group_ids = dealer.get_currency_group_ids(group, groups)
  701. share_group_ids.append(group.groupId)
  702. for user in users:
  703. overall += user.balance
  704. if user.groupId in dealer_group_ids:
  705. dealer_balance += user.balance
  706. if user.groupId in share_group_ids:
  707. currency_balance += user.balance
  708. return overall, dealer_balance, currency_balance
  709. @classmethod
  710. def get_new_user_count(cls, groupIds, start, end):
  711. # type: (list, datetime.datetime, datetime.datetime)->int
  712. result = cls.get_collection().aggregate(
  713. [
  714. {
  715. '$match': {
  716. "groupId": {"$in": groupIds},
  717. "dateTimeAdded": {"$gte": start, "$lte": end}
  718. }
  719. },
  720. {
  721. '$group': {
  722. '_id': '$openId'
  723. }
  724. },
  725. {'$count': 'count'},
  726. ]
  727. )
  728. return next(result, {}).get("count", 0)
  729. @classmethod
  730. def get_user_count_by_filter(cls, groupIds, filterDict = {}):
  731. # type: (list)->int
  732. filter = {"groupId": {"$in": groupIds}}
  733. filter.update(filterDict)
  734. result = cls.get_collection().aggregate(
  735. [
  736. {
  737. '$match': filter
  738. },
  739. {
  740. '$group': {
  741. '_id': '$openId'
  742. }
  743. },
  744. {'$count': 'count'},
  745. ]
  746. )
  747. return next(result, {}).get("count", 0)
  748. def get_extra_info(self, key):
  749. return self.extra.get(key)
  750. def set_extra_info(self, key, value):
  751. extra = self.extra
  752. extra[key] = value
  753. return self.update(extra = extra)
  754. @staticmethod
  755. def get_active_info(openId, **kwargs):
  756. """
  757. 获取用户的激活状态 安骑换电用户的激活状态在一个经销商下只会有一个激活状态
  758. """
  759. users = MyUser.objects(openId = openId, **kwargs)
  760. for user in users:
  761. if user.get_extra_info("active"):
  762. curUser = user
  763. break
  764. else:
  765. return dict() # 没找到用户的激活信息
  766. return curUser.get_extra_info("active")
  767. @staticmethod
  768. def del_active_info(openId, **kwargs):
  769. users = MyUser.objects(openId = openId, **kwargs)
  770. for user in users:
  771. if user.get_extra_info("active"):
  772. user.set_extra_info("active", {})
  773. user.phoneNumber = ""
  774. user.save()
  775. break
  776. @staticmethod
  777. def set_active_info(setInfo, openId, **kwargs):
  778. users = MyUser.objects(openId = openId, **kwargs)
  779. for user in users:
  780. if user.get_extra_info("active"):
  781. curUser = user
  782. break
  783. else: # 用户第一次注册
  784. curUser = user
  785. active = curUser.get_extra_info("active")
  786. if not active:
  787. active = dict()
  788. active.update(setInfo)
  789. curUser.set_extra_info("active", active)
  790. if setInfo.get("isMember"): # 被经销商激活的同时,为用户添加phoneNumber字段
  791. curUser.phoneNumber = active.get("phoneNumber")
  792. curUser.save()
  793. return
  794. @property
  795. def cloneable_user_info(self):
  796. """
  797. 对于微信, 必须是相同的平台, 用户信息才能一致;
  798. 对于京东和支付宝, 用户信息都是一致的
  799. :return:
  800. """
  801. return {
  802. 'authAppId': self.authAppId,
  803. 'avatar': self.avatar,
  804. 'nickname': self.nickname,
  805. 'managerialAppId': self.managerialAppId,
  806. 'managerialOpenId': self.managerialOpenId,
  807. 'payOpenIdMap': self.payOpenIdMap,
  808. 'gateway': self.gateway,
  809. 'productAgentId': self.productAgentId
  810. }
  811. @property
  812. def my_product_user(self):
  813. payload = self.cloneable_user_info
  814. payload['agentId'] = self.productAgentId
  815. return MyUser.get_or_create(self.gateway, self.openId, **payload)
  816. @property
  817. def collectedDeviceList(self):
  818. """
  819. 用户收藏的设备
  820. :return:
  821. """
  822. return self.my_product_user.favoriteDeviceList
  823. @collectedDeviceList.setter
  824. def collectedDeviceList(self, value):
  825. cur_user = self.my_product_user
  826. cur_user.favoriteDeviceList = value
  827. cur_user.save()
  828. @property
  829. def phone(self):
  830. """
  831. 用户的电话号码
  832. 之前是存储在productUser上面 现在需要做一次转换适配
  833. """
  834. uniqueUser = UniqueUser.get_or_create(self.openId)
  835. # 唯一用户存储了电话的情况下 直接返回唯一用户
  836. if uniqueUser.phone:
  837. return uniqueUser.phone
  838. # 从以前的存储电话号码的地方找出存储的电话 如有 转移到uniquerUser上面
  839. phoneNumber = self.my_product_user.phoneNumber
  840. if phoneNumber:
  841. uniqueUser.update_phone(phoneNumber)
  842. return phoneNumber
  843. else:
  844. return phoneNumber
  845. @phone.setter
  846. def phone(self, value):
  847. """
  848. 保存用户的电话号码 将直接保存到uniqueUser上面去
  849. """
  850. uniqueUser = UniqueUser.get_or_create(self.openId)
  851. uniqueUser.update_phone(value)
  852. @property
  853. def user_id(self):
  854. """
  855. 用户系统分配ID
  856. """
  857. if settings.ID_SERVICE_IP:
  858. uniqueUser = UniqueUser.get_or_create(self.openId)
  859. if not uniqueUser.userId:
  860. try:
  861. user_id = IDGenService(server_ip = settings.ID_SERVICE_IP).get_id()
  862. uniqueUser.userId = user_id
  863. uniqueUser.save()
  864. except Exception as e:
  865. logger.error('update user id failure.')
  866. logger.exception(e)
  867. return uniqueUser.userId
  868. else:
  869. return None
  870. def record_and_check_access_day(self, name, limit):
  871. dayTime = datetime.datetime.now().strftime(Const.DATE_FMT)
  872. key = '%s-%s-%s' % (self.openId, name, dayTime)
  873. value = serviceCache.get(key, 0)
  874. if value >= limit:
  875. return False
  876. value += 1
  877. serviceCache.set(key, value, 24 * 60 * 60)
  878. return True
  879. @property
  880. def cards_num(self):
  881. """
  882. 统计该用户的该平台下的实体卡数量
  883. :return:
  884. """
  885. filters = {"openId": self.openId, "agentId": self.productAgentId}
  886. return Card.objects.filter(**filters).count()
  887. @property
  888. def many_cards(self):
  889. """
  890. 是否允许 该用户拥有多张实体卡
  891. :return:
  892. """
  893. if "manyCards" in self.extra and self.get_extra_info("manyCards"):
  894. return True
  895. return False
  896. @classmethod
  897. def get_dealer_ids(cls, openId, productAgentId):
  898. users = cls.objects(openId = openId, productAgentId = productAgentId)
  899. group_ids = []
  900. for user in users:
  901. if not user.is_product_user():
  902. group_ids.append(user.groupId)
  903. groups = Group.get_groups_by_group_ids(group_ids)
  904. dealers = set()
  905. for group in groups.values():
  906. dealers.add(group.ownerId)
  907. return list(dealers)
  908. def calc_currency_balance(self, dealer, group, groups = None):
  909. # type:(Dealer, GroupDict, List[GroupDict])->RMB
  910. share_group_ids = dealer.get_currency_group_ids(group, groups)
  911. share_group_ids.append(group.groupId)
  912. users = MyUser.objects(openId =self.openId, groupId__in=share_group_ids)
  913. return sum((u.balance for u in users), RMB(0))
  914. def calc_currency_total_recharge(self, dealer, group):
  915. # type:(Dealer, GroupDict)->RMB
  916. share_group_ids = dealer.get_currency_group_ids(group)
  917. share_group_ids.append(group.groupId)
  918. users = MyUser.objects(openId = self.openId, groupId__in = share_group_ids)
  919. if users.count() == 0:
  920. return RMB(0)
  921. else:
  922. return RMB(users.sum('total_recharged'))
  923. @property
  924. def group(self):
  925. if self.is_product_user():
  926. return None
  927. if hasattr(self, '__group__'):
  928. return getattr(self, '__group__')
  929. group = Group.get_group(self.groupId) # type: GroupDict
  930. if group:
  931. setattr(self, '__group__', group)
  932. return group
  933. @property
  934. def username(self):
  935. return self.nickname
  936. @property
  937. def request_limit_key(self):
  938. return self.openId
  939. @classmethod
  940. def get_day_used_cache(cls, openId):
  941. """获取用户的每日使用次数信息"""
  942. key = '{}_day_used'.format(openId)
  943. result = cls.__mgr_cache.get(key, 0)
  944. return result
  945. @classmethod
  946. def update_day_used_cache(cls, openId):
  947. """
  948. 更新设备的每日使用次数信息
  949. :param openId:
  950. :return:
  951. """
  952. try:
  953. key = '{}_day_used'.format(openId)
  954. set_or_incr_cache(cls.__mgr_cache, key, 1, 24 * 60 * 60)
  955. except Exception:
  956. pass
  957. @classmethod
  958. def clear_day_used_cache(cls, openId):
  959. """清除用户的每日使用次数信息"""
  960. key = '{}_day_used'.format(openId)
  961. cls.__mgr_cache.set(key, 0)
  962. @classmethod
  963. def get_today_can_use(cls, openId):
  964. usedCount = MyUser.get_day_used_cache(openId)
  965. if usedCount > serviceCache.get('{}_day_used_limit'.format(openId), 10):
  966. return False
  967. return True
  968. def prepare_refund_cash(self, refund_order, minus_total_consume): # type:(RefundMoneyRecord, VirtualCoin)->bool
  969. query = {
  970. '_id': self.id,
  971. 'ongoingList.transaction_id': {
  972. '$ne': 'r_{}'.format(str(refund_order.id))
  973. }
  974. }
  975. update = {
  976. '$inc': {
  977. 'chargeBalance': (-refund_order.coins).mongo_amount,
  978. 'total_recharged': (-refund_order.money).mongo_amount,
  979. },
  980. '$addToSet': {
  981. 'ongoingList': {
  982. 'transaction_id': 'r_{}'.format(str(refund_order.id)),
  983. 'chargeBalance': refund_order.money.mongo_amount,
  984. 'minus_total_consume': minus_total_consume.mongo_amount
  985. }
  986. }
  987. }
  988. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  989. return bool(result.modified_count == 1)
  990. def commit_refund_cash(self, refund_order):
  991. # type:(RefundMoneyRecord)->bool
  992. query = {
  993. '_id': self.id,
  994. 'ongoingList': {
  995. '$elemMatch': {
  996. 'transaction_id': 'r_{}'.format(str(refund_order.id))
  997. }
  998. }
  999. }
  1000. update = {
  1001. '$pull': {
  1002. 'ongoingList': {'transaction_id': 'r_{}'.format(str(refund_order.id))}
  1003. }
  1004. }
  1005. result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
  1006. return bool(result.modified_count == 1)
  1007. @property
  1008. def isNormal(self):
  1009. return True
  1010. class MyUserDetail(Searchable):
  1011. openId = StringField(verbose_name="Myuser中的id", default="")
  1012. dealerId = StringField(verbose_name="经销商Id", default="")
  1013. userName = StringField(verbose_name="用户姓名", default="")
  1014. userPhone = StringField(verbose_name="用户电话", default="")
  1015. userUnit = StringField(verbose_name="用户地址单元", default="")
  1016. userFloor = StringField(verbose_name="用户地址楼层", default="")
  1017. userRoom = StringField(verbose_name="用户地址房间", default="")
  1018. meta = {
  1019. "collection": "MyUserDetail",
  1020. "db_alias": "logdata"}
  1021. @classmethod
  1022. def get_collection(cls):
  1023. # type: ()->Collection
  1024. return cls._get_collection()
  1025. class RechargeRecordDict(dict):
  1026. def __repr__(self):
  1027. return '<RechargeRecordDict id=%s>' % (self.get('id'),)
  1028. @property
  1029. def v(self):
  1030. return dict(self)
  1031. @property
  1032. def ownerId(self):
  1033. return self.get('ownerId')
  1034. @property
  1035. def groupId(self):
  1036. return self.get('groupId')
  1037. @property
  1038. def money(self):
  1039. return RMB(self['money'])
  1040. @property
  1041. def coins(self):
  1042. return VirtualCoin(self['coins'])
  1043. @property
  1044. def attachParas(self):
  1045. return self.get('attachParas')
  1046. @property
  1047. def extraInfo(self):
  1048. return self.get('extraInfo')
  1049. @property
  1050. def group(self):
  1051. # type:()->GroupDict
  1052. _attr_name = '__my_group__'
  1053. if hasattr(self, _attr_name):
  1054. return getattr(self, _attr_name)
  1055. else:
  1056. group = Group.get_group(self.groupId)
  1057. setattr(self, _attr_name, group)
  1058. return group
  1059. @property
  1060. def owner(self):
  1061. _attr_name = '__my_owner__'
  1062. if hasattr(self, _attr_name):
  1063. return getattr(self, _attr_name)
  1064. else:
  1065. dealer = Dealer.objects(id = self.ownerId).first()
  1066. setattr(self, _attr_name, dealer)
  1067. return dealer
  1068. @property
  1069. def my_gateway(self):
  1070. return self.get('gateway')
  1071. @property
  1072. def pay_app_type(self):
  1073. return self.get('payAppType')
  1074. @property
  1075. def pay_gateway_key(self):
  1076. return self.get('payGatewayKey')
  1077. @property
  1078. def withdraw_source_key(self):
  1079. return self.get('withdrawSourceKey')
  1080. @property
  1081. def dev_type_name(self):
  1082. if 'devTypeName' in self:
  1083. return self.get('devTypeName')
  1084. elif 'devType' in self:
  1085. return self.get('devType')
  1086. else:
  1087. return u'其他'
  1088. @property
  1089. def logicalCode(self):
  1090. return self.get('logicalCode')
  1091. @property
  1092. def address(self):
  1093. return self.get('address')
  1094. @property
  1095. def via(self):
  1096. return self.get('via')
  1097. def fetch_account_split_map(self, dealer, group, payment_gateway=None): # type:(Dealer, GroupDict, PaymentGateway) -> dict
  1098. """
  1099. 分账的模式以厂家设置为准 不再以代理商设置为准(之前都是默认模式,无需考虑兼容)
  1100. """
  1101. if dealer.my_agent.manager.deviceIncomeType == DEVICE_INCOME_STRATEGY.AGENT_FIRST_LEDGER:
  1102. return AgentLedgerFirst(
  1103. dealer, group, self, payment_gateway=payment_gateway
  1104. ).calc_account_split_map()
  1105. else:
  1106. return PartnerLedgerFirst(
  1107. dealer, group, self, payment_gateway = payment_gateway
  1108. ).calc_account_split_map()
  1109. @property
  1110. def partition_map(self):
  1111. """
  1112. 新订单模型下, 创建订单就会生成partitionMap. 所以直接取
  1113. :return:
  1114. """
  1115. return self.extraInfo.get('partitionMap', None)
  1116. def calc_income_partitions(self, partition_map):
  1117. totalMoney = self.money
  1118. leftMoney = totalMoney
  1119. agent_partitions = partition_map.get(PARTITION_ROLE.AGENT, list())
  1120. if agent_partitions:
  1121. for agentShare in agent_partitions:
  1122. shareMoney = totalMoney * Percent(agentShare['share']).as_ratio
  1123. if shareMoney > leftMoney:
  1124. shareMoney = leftMoney
  1125. leftMoney = leftMoney - shareMoney
  1126. agentShare['money'] = shareMoney.mongo_amount
  1127. partner_partitions = partition_map.get(PARTITION_ROLE.PARTNER, list())
  1128. if partner_partitions:
  1129. for partnerShare in partner_partitions:
  1130. shareMoney = totalMoney * Percent(partnerShare['share']).as_ratio
  1131. if shareMoney > leftMoney:
  1132. shareMoney = leftMoney
  1133. leftMoney = leftMoney - shareMoney
  1134. partnerShare['money'] = shareMoney.mongo_amount
  1135. owner_partitions = partition_map.get(PARTITION_ROLE.OWNER, list())
  1136. assert len(owner_partitions) == 1, u'经销商只能有一个'
  1137. ownerShare = owner_partitions[0]
  1138. ownerShare['money'] = leftMoney.mongo_amount
  1139. return partition_map
  1140. def attach_split_info(self, partitions):
  1141. agent_partitions = partitions.get(PARTITION_ROLE.AGENT, list())
  1142. if agent_partitions:
  1143. for agentShare in agent_partitions:
  1144. if RMB(agentShare['money']) > RMB(0):
  1145. agent = Agent.objects.get(id = agentShare["id"])
  1146. _email = agent.get_merchant_split_id()
  1147. if not _email:
  1148. logger.error(u"分账代理商未开通商户,agent id = {}".format(agentShare["id"]))
  1149. raise UserServerException(u"分账代理商未开通商户")
  1150. agentShare['merchantId'] = _email
  1151. partner_partitions = partitions.get(PARTITION_ROLE.PARTNER, list())
  1152. if partner_partitions:
  1153. for partnerShare in partner_partitions:
  1154. if RMB(partnerShare['money']) > RMB(0):
  1155. dealer = Dealer.objects.get(id = partnerShare["id"])
  1156. _email = dealer.get_merchant_split_id()
  1157. if not _email:
  1158. logger.error(u"分账代理商未开通商户,dealer id = {}".format(partnerShare["id"]))
  1159. raise UserServerException(u"分账合伙人未开通商户")
  1160. partnerShare['merchantId'] = _email
  1161. owner_partitions = partitions.get(PARTITION_ROLE.OWNER, list())
  1162. assert len(owner_partitions) == 1, u'经销商只能有一个'
  1163. ownerShare = owner_partitions[0]
  1164. if Percent(ownerShare["share"]) < Percent(1):
  1165. raise ValueError(u"dealer jd share should not lt 0.01")
  1166. if RMB(ownerShare['money']) <= RMB(0):
  1167. raise UserServerException(u"经销商商户配置错误")
  1168. ownerShare['merchantId'] = self.owner.get_merchant_split_id()
  1169. return partitions
  1170. def calc_refund_partitions(self, pay_split_map):
  1171. totalMoney = self.money
  1172. leftMoney = totalMoney
  1173. share_key = lambda _role, _id: '{}_{}'.format(_role, _id)
  1174. old_shares_map = {}
  1175. partition_map = {
  1176. PARTITION_ROLE.AGENT: [],
  1177. PARTITION_ROLE.PARTNER: [],
  1178. PARTITION_ROLE.OWNER: []
  1179. }
  1180. agent_partitions = pay_split_map.get(PARTITION_ROLE.AGENT, list())
  1181. if agent_partitions:
  1182. for agentShare in agent_partitions:
  1183. old_shares_map[share_key(agentShare['role'], agentShare['id'])] = RMB(agentShare['money'])
  1184. newAgentShare = copy.deepcopy(agentShare)
  1185. shareMoney = totalMoney * Percent(agentShare['share']).as_ratio
  1186. if abs(shareMoney) > abs(leftMoney):
  1187. shareMoney = leftMoney
  1188. leftMoney = leftMoney - shareMoney
  1189. newAgentShare['money'] = shareMoney.mongo_amount
  1190. partition_map[PARTITION_ROLE.AGENT].append(newAgentShare)
  1191. partner_partitions = pay_split_map.get(PARTITION_ROLE.PARTNER, list())
  1192. if partner_partitions:
  1193. for partnerShare in partner_partitions:
  1194. old_shares_map[share_key(partnerShare['role'], partnerShare['id'])] = RMB(partnerShare['money'])
  1195. newPartnerShare = copy.deepcopy(partnerShare)
  1196. shareMoney = totalMoney * Percent(partnerShare['share']).as_ratio
  1197. if abs(shareMoney) > abs(leftMoney):
  1198. shareMoney = leftMoney
  1199. leftMoney = leftMoney - shareMoney
  1200. newPartnerShare['money'] = shareMoney.mongo_amount
  1201. partition_map[PARTITION_ROLE.PARTNER].append(newPartnerShare)
  1202. owner_partitions = pay_split_map.get(PARTITION_ROLE.OWNER, list())
  1203. assert len(owner_partitions) == 1, u'经销商只能有一个'
  1204. ownerShare = owner_partitions[0]
  1205. newOwnerShare = copy.deepcopy(ownerShare)
  1206. old_share_money = RMB(ownerShare['money'])
  1207. if abs(leftMoney) <= old_share_money:
  1208. newOwnerShare['money'] = leftMoney.mongo_amount
  1209. else:
  1210. diff = abs(leftMoney) - old_share_money
  1211. shares = list(flatten(partition_map.values()))
  1212. shares.sort(key = lambda x: Percent(x['share']), reverse = True)
  1213. for share in shares:
  1214. if RMB(share['money']) - RMB('0.01') + RMB(
  1215. old_shares_map[share_key(share['role'], share['id'])]) >= RMB(0):
  1216. share['money'] = (RMB(share['money']) - RMB('0.01')).mongo_amount
  1217. diff -= RMB('0.01')
  1218. leftMoney += RMB('0.01')
  1219. if diff <= RMB(0):
  1220. break
  1221. assert diff == RMB(0), u'分账金额错误'
  1222. newOwnerShare['money'] = leftMoney.mongo_amount
  1223. partition_map[PARTITION_ROLE.OWNER] = [newOwnerShare]
  1224. for role, items in partition_map.iteritems():
  1225. for item in items:
  1226. if RMB(item['money']) == RMB(0):
  1227. item['money'] = RMB(0).mongo_amount
  1228. return partition_map
  1229. class RechargeRecord(OrderRecordBase):
  1230. class PayResult(IterConstant):
  1231. UNPAY = 'unPay'
  1232. SUCCESS = 'success'
  1233. FAILED = 'failed'
  1234. REFUNDING = 'refunding'
  1235. CANCEL = 'cancel'
  1236. orderNo = StringField(verbose_name=u"订单号", unique=True)
  1237. wxOrderNo = StringField(verbose_name=u"渠道订单号")
  1238. transactionId = StringField(verbose_name=u"微信或者支付宝订单号", default=None)
  1239. ownerId = StringField(verbose_name = u"设备的owner", default = "")
  1240. #: 充值的金钱数额 incr(user.balance)
  1241. money = MonetaryField(verbose_name = u"充值", default = RMB('0.00'))
  1242. #: 用户购得的金币,如果是卡,就是充值的钱
  1243. coins = VirtualCoinField(verbose_name = u"金币数目", default = VirtualCoin('0.00'))
  1244. subject = StringField(verbose_name = u"订单产品或者服务描述", default = "")
  1245. result = StringField(verbose_name = u"充值或者服务结果", default = PayResult.UNPAY)
  1246. extraInfo = DictField(verbose_name = u"支付订单模型信息", default = {})
  1247. description = StringField(verbose_name = u"结果描述,一般为第三方错误码", default = None)
  1248. isQuickPay = BooleanField(verbose_name = u"是否直接支付使用,默认为否", default = False)
  1249. selectedQuickPayPackageId = StringField(verbose_name = u"快捷支付所选的投币套餐", default = None)
  1250. via = StringField(verbose_name = u"充值途径 =: (recharge|sendcoin|refund|chargeCard|swap)", default='recharge')
  1251. finishedTime = DateTimeField(verbose_name=u"支付完成时间")
  1252. # 派币需要记录信息
  1253. operator = StringField(verbose_name=u"操作员")
  1254. attachParas = DictField(verbose_name=u"消费订单信息", default={})
  1255. gateway = StringField(verbose_name=u'支付网关类型', default='')
  1256. payAppType = StringField(verbose_name=u'支付应用类型', default=None)
  1257. payGatewayKey = StringField(verbose_name=u'支付网关key', default=None)
  1258. withdrawSourceKey = StringField(verbose_name=u'提现商户平台标识,资金池模式下和代理商相同')
  1259. isAllocatedCardMoney = BooleanField(verbose_name=u'是否分钱了', default=False)
  1260. search_fields = ('orderNo', 'wxOrderNo')
  1261. _shard_key = ('ownerId', 'dateTimeAdded')
  1262. _origin_meta = {
  1263. "collection": "RechargeRecord",
  1264. "db_alias": "default"
  1265. }
  1266. meta = _origin_meta
  1267. def __repr__(self):
  1268. return '<RechargeRecord id=%s, orderNo=%s, wxOrderNo=%s, via=%s>' % (str(self.id), self.orderNo, self.wxOrderNo, self.via)
  1269. @cached_property
  1270. def payGateway(self):
  1271. return PaymentGateway.clone_from_order(self)
  1272. @cached_property
  1273. def user(self):
  1274. return MyUser.objects.filter(openId=self.openId, groupId=self.groupId).first()
  1275. @cached_property
  1276. def dealer(self):
  1277. return Dealer.objects(id=self.ownerId).get()
  1278. @property
  1279. def goods(self): # type:()->dict
  1280. return {}
  1281. @property
  1282. def timeout(self): # type:()-> bool
  1283. """支付超时"""
  1284. return (datetime.datetime.now() - self.dateTimeAdded).total_seconds() > 60 * 3
  1285. @property
  1286. def chargeAmount(self):
  1287. return self.money
  1288. @property
  1289. def bestowAmount(self):
  1290. return RMB(self.coins) - RMB(self.money)
  1291. @property
  1292. def my_gateway(self):
  1293. return self.gateway
  1294. @property
  1295. def pay_app_type(self):
  1296. return self.payAppType
  1297. @property
  1298. def pay_gateway_key(self):
  1299. return self.payGatewayKey
  1300. @property
  1301. def owner(self):
  1302. return self.dealer
  1303. @property
  1304. def payOrder(self):
  1305. """
  1306. 用户的实际支付的订单 由于业务需要 可能被拆分为多笔
  1307. # TODO 需要搞定历史数据库的问题
  1308. """
  1309. if self.isSubOrder:
  1310. _id = self.attachParas.get("tradeOrderId", None) or self.extraInfo.get('tradeOrderId', None)
  1311. return self.__class__.objects.get(id = _id)
  1312. return self
  1313. @property
  1314. def isSubOrder(self):
  1315. return "tradeOrderId" in self.extraInfo or "tradeOrderId" in self.attachParas
  1316. @property
  1317. def card_no(self):
  1318. return self.attachParas.get('cardNo', None)
  1319. @property
  1320. def my_description(self):
  1321. if self.description:
  1322. return self.description
  1323. else:
  1324. return getattr(self, 'desc', '')
  1325. @property
  1326. def my_via(self):
  1327. """
  1328. 老的订单模型, 对于recharge, 根据是否快速支付做一个调整
  1329. :return:
  1330. """
  1331. return self.via
  1332. @property
  1333. def withdraw_source_key(self):
  1334. return self.withdrawSourceKey
  1335. @property
  1336. def partition_map(self):
  1337. return self.to_dict_obj.partition_map
  1338. @property
  1339. def is_cancel(self):
  1340. return self.result == self.PayResult.CANCEL
  1341. @property
  1342. def fen_total_fee(self):
  1343. return int(self.money * 100)
  1344. @property
  1345. def amount(self):
  1346. # type:()->RMB
  1347. return self.money
  1348. @property
  1349. def my_amount(self):
  1350. if self.via in [RechargeRecordVia.RefundCash,
  1351. RechargeRecordVia.Cash,
  1352. RechargeRecordVia.VirtualCard,
  1353. RechargeRecordVia.MonthlyPackage]:
  1354. return '{}元'.format(abs(self.money))
  1355. else:
  1356. return self.coins
  1357. @property
  1358. def mongo_amount(self):
  1359. return self.amount.mongo_amount
  1360. @property
  1361. def extra_detail_info(self):
  1362. rv = {}
  1363. # TODO: 需要根据RechargeRecord建立各VIA下的订单详细信息
  1364. if self.via == 'chargeCard':
  1365. cardId = str(self.attachParas.get('cardId'))
  1366. card = Card.objects(id = cardId).first()
  1367. if card:
  1368. rv.update({'cardNo': card.cardNo, 'cardType': card.cardType})
  1369. elif self.via == 'chargeVirtualCard':
  1370. if 'type' not in self.attachParas:
  1371. # 老模型在new的情况下, 是没有type字段的
  1372. cardNo = self.attachParas.get('cardNo', None)
  1373. if cardNo:
  1374. rv.update({'cardNo': cardNo})
  1375. cardId = self.attachParas.get('cardId', None)
  1376. if cardId:
  1377. cardTmpl = VirtualCard.objects(id = cardId).first() # type: VirtualCard
  1378. if cardTmpl:
  1379. rv.update({'cardName': cardTmpl.cardName})
  1380. else:
  1381. if 'cardId' in self.attachParas:
  1382. virtualCard = UserVirtualCard.objects(
  1383. id = self.attachParas['cardId']).first() # type: UserVirtualCard
  1384. rv.update({
  1385. 'cardNo': virtualCard.cardNo, 'cardName': virtualCard.cardName
  1386. })
  1387. return rv
  1388. @property
  1389. def summary_info(self):
  1390. rv = {
  1391. 'id': str(self.id),
  1392. 'createdTime': self.to_datetime_str(self.dateTimeAdded),
  1393. 'money': self.money,
  1394. 'coins': self.coins,
  1395. 'groupName': self.groupName,
  1396. 'groupNumber': self.groupNumber,
  1397. 'address': self.address,
  1398. 'logicalCode': self.logicalCode,
  1399. 'devTypeName': self.dev_type_name,
  1400. 'devTypeCode': self.dev_type_code,
  1401. 'ownerId': self.ownerId,
  1402. 'via': self.via
  1403. }
  1404. if self.via == 'chargeVirtualCard':
  1405. rv.pop('coins')
  1406. return rv
  1407. @property
  1408. def created_date(self):
  1409. return self.dateTimeAdded
  1410. @property
  1411. def finished_time(self):
  1412. if self.result != self.PayResult.SUCCESS:
  1413. return ''
  1414. if self.finishedTime:
  1415. return self.to_datetime_str(self.finishedTime)
  1416. return self.to_datetime_str(self.dateTimeAdded)
  1417. @property
  1418. def ledger_enable(self):
  1419. return self.result in [self.PayResult.SUCCESS, self.PayResult.REFUNDING]
  1420. @property
  1421. def is_ledgered(self):
  1422. """
  1423. 判断该订单是否已经 分账获取了收益
  1424. :return:
  1425. """
  1426. if self.result != self.PayResult.SUCCESS:
  1427. # 失败订单肯定是没有分账的
  1428. return False
  1429. if self.isAllocatedCardMoney:
  1430. return True
  1431. # 没有该字段的订单 直接进行一次检查 看看有没有分账
  1432. from apps.web.common.proxy import ClientDealerIncomeModelProxy
  1433. try:
  1434. ledgerRecord = ClientDealerIncomeModelProxy.get_one(ref_id = self.id)
  1435. except Exception as e:
  1436. logger.exception(e)
  1437. return False
  1438. return bool(ledgerRecord)
  1439. @property
  1440. def notLedgerDesc(self):
  1441. """
  1442. 没有分账的描述值 12小时 72小时分界线
  1443. :return:
  1444. """
  1445. # 未超过 12 小时的情况 可以当成是订单尚未结束
  1446. if self.dateTimeAdded + datetime.timedelta(hours = 12) > datetime.datetime.now():
  1447. desc = u"订单尚未获取收益,可能设备正在运行"
  1448. # 超过 12 小时但是 没有到 72 小时的情况 告知订单可能可能出现了某种问题 等待系统监测自动结账
  1449. elif self.dateTimeAdded + datetime.timedelta(hours = 72) > datetime.datetime.now():
  1450. desc = u"订单尚未获取收益,可能由于网络原因造成订单未关闭,系统将于{}小时后强制分账,请勿担心!".format(
  1451. 72 - (datetime.datetime.now().hour - self.dateTimeAdded.hour))
  1452. # 超过72小时还没有分账的 问题单
  1453. else:
  1454. desc = u"订单尚未获取收益,请联系平台运营人员进行处理"
  1455. return desc
  1456. @property
  1457. def used_port(self):
  1458. if self.attachParas and 'chargeIndex' in self.attachParas and self.attachParas['chargeIndex']:
  1459. return str(self.attachParas['chargeIndex'])
  1460. return ''
  1461. @property
  1462. def startKey(self):
  1463. return self.attachParas.get('startKey', None)
  1464. @property
  1465. def refundOrders(self):
  1466. return RefundMoneyRecord.objects.filter(rechargeObjId = self.id)
  1467. @property
  1468. def has_refund_order(self):
  1469. if self.refundOrders:
  1470. return True
  1471. else:
  1472. return False
  1473. @property
  1474. def is_temp_package(self):
  1475. return 'isTempPackage' in self.attachParas and self.attachParas['isTempPackage'] is True
  1476. @property
  1477. def to_dict_obj(self):
  1478. return RechargeRecordDict({
  1479. 'via': self.via,
  1480. 'money': self.money,
  1481. 'coins': self.coins,
  1482. 'attachParas': self.attachParas,
  1483. 'extraInfo': self.extraInfo,
  1484. 'gateway': self.my_gateway,
  1485. 'payAppType': self.pay_app_type,
  1486. 'payGatewayKey': self.pay_gateway_key
  1487. })
  1488. @classmethod
  1489. def get_record(cls, order_no, dealer_id = None):
  1490. # type: (str, str)->Optional[RechargeRecord]
  1491. # TODO 数据库分片修改
  1492. # record = RechargeRecord.objects(ownerId = dealer_id, orderNo = order_no).first()
  1493. record = cls.objects(orderNo = order_no).first()
  1494. return record
  1495. @classmethod
  1496. def calc_count(cls, filter, group = None, limit = None, sort = None, allowDiskUse = False):
  1497. # type: (dict, dict, int, dict, bool)->int
  1498. pipeline = []
  1499. if filter:
  1500. pipeline.append({
  1501. '$match': filter
  1502. })
  1503. if group:
  1504. pipeline.append({
  1505. '$group': group
  1506. })
  1507. if limit:
  1508. pipeline.append({
  1509. '$limit': limit
  1510. })
  1511. if sort:
  1512. pipeline.append({
  1513. '$sort': sort
  1514. })
  1515. pipeline.append({'$count': 'count'})
  1516. result = cls.get_collection().aggregate(pipeline, allowDiskUse = allowDiskUse)
  1517. return next(result, {}).get('count', 0)
  1518. @classmethod
  1519. def get_recharged_records(cls, **kwargs):
  1520. return cls.objects(via = 'recharge', result = cls.PayResult.SUCCESS, **kwargs).order_by('-dateTimeAdded')
  1521. @classmethod
  1522. def get_card_recharged_records(cls, **kwargs):
  1523. return cls.objects(via = 'chargeCard', result = cls.PayResult.SUCCESS, **kwargs).order_by('-dateTimeAdded')
  1524. @classmethod
  1525. def issue_pay_order(cls, context, gateway, **payload):
  1526. payload.update({
  1527. "openId": context.user.openId,
  1528. "nickname": context.user.nickname,
  1529. 'gateway': gateway.gateway_type,
  1530. 'payAppType': gateway.pay_app_type,
  1531. 'payGatewayKey': gateway.gateway_key,
  1532. 'withdrawSourceKey': gateway.withdraw_source_key()
  1533. })
  1534. if gateway.pay_app_type in [PayAppType.WECHAT, PayAppType.WECHAT_MINI, PayAppType.ALIPAY]:
  1535. payload['extraInfo'].update({
  1536. 'payOpenId': context.user.get_bound_pay_openid(gateway.bound_openid_key)
  1537. })
  1538. elif gateway.pay_app_type == PayAppType.JD_AGGR and gateway.gateway_type in [AppPlatformType.WECHAT, AppPlatformType.ALIPAY]:
  1539. payload['extraInfo'].update({
  1540. 'payOpenId': context.user.get_bound_pay_openid(gateway.bound_openid_key)
  1541. })
  1542. if "result" not in payload:
  1543. payload["result"] = cls.PayResult.UNPAY
  1544. order = cls(payload)
  1545. return order.save()
  1546. @classmethod
  1547. def issue_from_auto_sim_order(cls, owner, sim_recharge, device, group):
  1548. # type:(Dealer, DealerRechargeRecord, Device, GroupDict)->RechargeRecord
  1549. payload = {
  1550. 'openId': owner.username,
  1551. 'nickname': owner.nickname,
  1552. 'orderNo': sim_recharge.orderNo,
  1553. 'ownerId': sim_recharge.dealerId,
  1554. 'money': -RMB(sim_recharge.totalFee/100),
  1555. 'coins': VirtualCoin(0),
  1556. 'subject': sim_recharge.subject,
  1557. 'result': cls.PayResult.SUCCESS,
  1558. 'via': RechargeRecordVia.AutoSim,
  1559. 'finishedTime': sim_recharge.finishedTime,
  1560. 'gateway': sim_recharge.my_gateway,
  1561. 'payAppType': sim_recharge.pay_app_type,
  1562. 'payGatewayKey': sim_recharge.pay_gateway_key,
  1563. 'isAllocatedCardMoney': True,
  1564. 'devNo': device.devNo,
  1565. 'devType': device.devTypeName,
  1566. 'devTypeName': device.devTypeName,
  1567. 'devTypeCode': device.devTypeCode,
  1568. 'logicalCode': device.logicalCode,
  1569. 'groupId': device.groupId,
  1570. 'address': group.address,
  1571. 'groupNumber': device.groupNumber,
  1572. 'groupName': group.groupName
  1573. }
  1574. record = cls(**payload).save()
  1575. record.group = group
  1576. return record
  1577. @classmethod
  1578. def viaText(cls, via, quickPay):
  1579. if via == RechargeRecordVia.Balance:
  1580. if quickPay:
  1581. return u'快捷支付'
  1582. else:
  1583. return u'优惠充值'
  1584. else:
  1585. return RECHARGE_RECORD_VIA_TRANSLATION[via]
  1586. @classmethod
  1587. def gatewayText(cls, gateway):
  1588. return APP_PLATFORM_TYPE_TRANSLATION.get(gateway, u'未知')
  1589. @classmethod
  1590. def from_feedback(cls, feedback, refundCoins):
  1591. # type: (FeedBack, VirtualCoin)->RechargeRecord
  1592. return cls(
  1593. orderNo = str(uuid.uuid4()),
  1594. coins = refundCoins,
  1595. money = RMB(0),
  1596. openId = feedback.openId,
  1597. groupId = feedback.groupId,
  1598. devNo = feedback.devNo,
  1599. ownerId = feedback.ownerId,
  1600. groupName = feedback.groupName,
  1601. groupNumber = feedback.groupNumber,
  1602. address = feedback.address,
  1603. wxOrderNo = u'老板退币',
  1604. devTypeName = feedback.devTypeName,
  1605. nickname = feedback.nickname,
  1606. result = cls.PayResult.SUCCESS,
  1607. via = 'refund')
  1608. @classmethod
  1609. def issue_pay_record(cls, context, **payload):
  1610. # type: (OrderBuilderContext, Dict)->RechargeRecord
  1611. user = context.user
  1612. payment_gateway = context.pay_gateway # type: PaymentGateway
  1613. payload.update({
  1614. 'openId': user.openId,
  1615. 'nickname': user.nickname,
  1616. 'gateway': payment_gateway.gateway_type,
  1617. 'payAppType': payment_gateway.pay_app_type,
  1618. 'payGatewayKey': payment_gateway.gateway_key,
  1619. 'withdrawSourceKey': payment_gateway.withdraw_source_key()
  1620. })
  1621. if 'attachParas' not in payload:
  1622. payload['attachParas'] = {}
  1623. if 'extraInfo' not in payload:
  1624. payload['extraInfo'] = {}
  1625. if payment_gateway.pay_app_type in [PayAppType.WECHAT, PayAppType.WECHAT_MINI, PayAppType.ALIPAY]:
  1626. payload['extraInfo'].update({
  1627. 'payOpenId': user.get_bound_pay_openid(payment_gateway.bound_openid_key)
  1628. })
  1629. elif payment_gateway.pay_app_type == PayAppType.JD_AGGR and \
  1630. payment_gateway.gateway_type in [AppPlatformType.WECHAT, AppPlatformType.ALIPAY]:
  1631. payload['extraInfo'].update({
  1632. 'payOpenId': user.get_bound_pay_openid(payment_gateway.bound_openid_key)
  1633. })
  1634. if "result" not in payload:
  1635. payload['result'] = cls.PayResult.UNPAY
  1636. record = cls(**payload) # type: RechargeRecord
  1637. return record.save()
  1638. def to_dict(self, json_safe=False):
  1639. money = str(self.money) if json_safe else self.money
  1640. return {
  1641. 'ownerId': self.ownerId,
  1642. 'nickname': self.nickname,
  1643. 'money': money,
  1644. 'gateway': self.my_gateway,
  1645. 'via': self.via,
  1646. }
  1647. def to_json_dict(self):
  1648. return self.to_dict(json_safe = True)
  1649. def fetch_account_split_map(self, dealer, group, payment_gateway = None):
  1650. return self.to_dict_obj.fetch_account_split_map(dealer, group, payment_gateway)
  1651. def succeed(self, wxOrderNo, **kwargs):
  1652. # 这里和数据库强相关, 通过判断是否正确更新记录
  1653. payload = {
  1654. 'wxOrderNo': wxOrderNo
  1655. }
  1656. if kwargs:
  1657. payload.update(kwargs)
  1658. payload.update({'result': self.PayResult.SUCCESS})
  1659. result = self.get_collection().update_one(
  1660. filter = {'_id': ObjectId(self.id), 'result': {'$ne': self.PayResult.SUCCESS}},
  1661. update = {'$set': payload},
  1662. upsert = False)
  1663. return result.matched_count == 1
  1664. def fail(self, **kwargs):
  1665. self.update(result = self.PayResult.FAILED, **kwargs)
  1666. return self.reload()
  1667. def cancel(self):
  1668. payload = {
  1669. 'result': self.PayResult.CANCEL,
  1670. 'finishedTime': datetime.datetime.now()
  1671. }
  1672. result = self.get_collection().update_one(
  1673. filter = {'_id': ObjectId(self.id), 'result': self.PayResult.UNPAY},
  1674. update = {'$set': payload},
  1675. upsert = False)
  1676. return result.matched_count == 1
  1677. def is_success(self):
  1678. return self.result == self.PayResult.SUCCESS
  1679. def is_fail(self):
  1680. return self.result == self.PayResult.FAILED
  1681. def to_detail(self):
  1682. # type: ()->dict
  1683. """
  1684. :return:
  1685. """
  1686. return {
  1687. 'id': self.id,
  1688. 'rechargeTradeNo': self.orderNo,
  1689. 'payResult': self.result,
  1690. 'ownerId': self.ownerId,
  1691. 'gateway': self.my_gateway,
  1692. 'openId': self.openId,
  1693. 'groupId': self.groupId,
  1694. 'userNickname': self.nickname,
  1695. 'orderAmount': self.money,
  1696. 'coins': self.coins,
  1697. 'devNo': self.devNo,
  1698. 'logicalCode': self.logicalCode,
  1699. 'devTypeName': self.dev_type_name,
  1700. 'groupName': self.groupName,
  1701. 'groupNumber': self.groupNumber,
  1702. 'address': self.address,
  1703. 'createdTime': self.dateTimeAdded,
  1704. 'completionTime': self.finished_time,
  1705. 'via': self.via,
  1706. 'isQuickPay': self.isQuickPay,
  1707. 'startKey': self.attachParas.get('startKey', None),
  1708. # 以下字段和以前做兼容
  1709. 'outTradeNo': self.orderNo,
  1710. 'finishedTime': self.finished_time,
  1711. 'gatewayTradeNo': self.wxOrderNo,
  1712. 'result': self.result
  1713. }
  1714. def to_dict_for_super_admin(self):
  1715. from apps.web.common.proxy import ClientConsumeModelProxy
  1716. orderResult = ''
  1717. isNormalDesc = ''
  1718. via = ''
  1719. if self.result == 'unPay':
  1720. orderResult = u'未付款'
  1721. elif self.result == 'failed':
  1722. orderResult = u'充值失败'
  1723. elif self.result == 'success':
  1724. orderResult = u'充值成功'
  1725. if self.via == '':
  1726. via = u'充值金币'
  1727. elif self.via == '':
  1728. via = u'充值卡'
  1729. if 'consumeRecordId' in self.attachParas:
  1730. # 部分后支付的设备类型, 在支付的时候会塞入消费单的id
  1731. consumeRcd = ClientConsumeModelProxy.get_one(
  1732. shard_filter = {'ownerId': self.ownerId},
  1733. id = self.attachParas.get("consumeRecordId")) # type: ConsumeRecord
  1734. elif 'startKey' in self.attachParas and self.attachParas['startKey']:
  1735. consumeRcd = ClientConsumeModelProxy.get_one(
  1736. shard_filter = {'ownerId': self.ownerId},
  1737. foreign_id = str(self.id),
  1738. startKey = self.attachParas['startKey']) # type: ConsumeRecord
  1739. else:
  1740. consumeRcd = None
  1741. if consumeRcd is not None:
  1742. if consumeRcd.isNormal is True:
  1743. isNormalDesc = u'消费成功'
  1744. else:
  1745. isNormalDesc = u'消费失败'
  1746. return {
  1747. 'wxOrderNo': self.wxOrderNo,
  1748. 'orderNo': self.orderNo,
  1749. 'orderResult': orderResult,
  1750. 'rechargeUser': self.nickname + ' ' + self.openId,
  1751. 'rechargeType': via,
  1752. 'money': str(self.money),
  1753. 'coins': str(self.coins),
  1754. 'orderDetail': self.attachParas,
  1755. 'orderCreateTime': self.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S'),
  1756. 'logicalCode': self.logicalCode,
  1757. 'devNo': self.devNo,
  1758. 'devTypeCode': self.dev_type_code,
  1759. 'consumeOrderNo': consumeRcd.orderNo if consumeRcd else '',
  1760. 'consumeUser': consumeRcd.nickname + ' ' + consumeRcd.openId if consumeRcd else '',
  1761. 'consumeCoins': str(consumeRcd.coin) if consumeRcd else '',
  1762. 'consumeResult': isNormalDesc if consumeRcd else '',
  1763. 'failedReason': consumeRcd.errorDesc if consumeRcd else '',
  1764. 'consumeOrderCreateTime': consumeRcd.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S') if consumeRcd else '',
  1765. 'consumeDetail': consumeRcd.attachParas if consumeRcd else {},
  1766. 'consumeDict': consumeRcd.servicedInfo if consumeRcd else {}
  1767. }
  1768. def set_ledgered(self):
  1769. try:
  1770. modified = self.update(isAllocatedCardMoney = True)
  1771. if not modified:
  1772. logger.error('failed to update isAllocatedCardMoney for record=%r' % (self,))
  1773. except Exception as e:
  1774. logger.exception(e)
  1775. def new_refund_cash_order(self, refund_order): # type: (RefundMoneyRecord)->RechargeRecord
  1776. payload = {}
  1777. for field in self.__class__._fields_ordered:
  1778. if field in ['id', 'orderNo', 'wxOrderNo', 'transactionId', 'dateTimeAdded', 'ledgerStatus',
  1779. 'finishedTime', 'notifiedTime', 'operator', 'devTypeName', 'money', 'coins',
  1780. 'selectedQuickPayPackageId', 'devType', 'attachParas', 'isAllocatedCardMoney', 'subject']:
  1781. continue
  1782. payload.update({
  1783. field: getattr(self.payOrder, field)
  1784. })
  1785. payload.update({
  1786. 'orderNo': refund_order.orderNo,
  1787. 'money': -refund_order.money,
  1788. 'coins': -refund_order.coins,
  1789. 'devTypeName': self.dev_type_name,
  1790. 'via': RechargeRecordVia.RefundCash,
  1791. 'isAllocatedCardMoney': False,
  1792. 'gateway': self.my_gateway,
  1793. 'payGatewayKey': self.pay_gateway_key,
  1794. 'payAppType': self.pay_app_type,
  1795. 'withdrawSourceKey': self.withdraw_source_key,
  1796. 'result': self.PayResult.REFUNDING,
  1797. 'extraInfo': {
  1798. 'refPay': {'objId': str(self.id)}
  1799. },
  1800. 'subject': '{} {}'.format(self.subject, u'退费')
  1801. })
  1802. refund_order_record = self.__class__(**payload).save() # type: RechargeRecord
  1803. refund_order.refund_order_record = refund_order_record
  1804. self.update(push__extraInfo__refRefund = {
  1805. 'objId': str(refund_order_record.id),
  1806. 'money': refund_order.money.mongo_amount,
  1807. 'coins': refund_order.coins.mongo_amount
  1808. })
  1809. return refund_order_record
  1810. def is_refund_available(self, customer): # type:(CapitalUser) -> bool
  1811. if customer.role == ROLE.dealer:
  1812. if self.ownerId != str(customer.id):
  1813. logger.warning('is not my order. {} != {}'.format(self.ownerId, str(customer.id)))
  1814. return False
  1815. if self.via not in [USER_RECHARGE_TYPE.RECHARGE, USER_RECHARGE_TYPE.RECHARGE_CASH]:
  1816. return False
  1817. return True
  1818. return False
  1819. class Dispute(EmbeddedDocument):
  1820. feedback_id = ObjectIdField()
  1821. #
  1822. # class ConsumeRecord(OrderRecordBase):
  1823. # """
  1824. # 用户的消费订单记录
  1825. # """
  1826. #
  1827. # if TYPE_CHECKING:
  1828. # state = None
  1829. #
  1830. # def is_waitPay(self): pass
  1831. #
  1832. # def p_to_f(self): pass
  1833. #
  1834. # def c_to_s(self): pass
  1835. #
  1836. # def s_to_e(self): pass
  1837. #
  1838. # def is_end(self): pass
  1839. #
  1840. # def e_to_p(self): pass
  1841. #
  1842. # class Status(IterConstant):
  1843. # CREATED = 'created'
  1844. # WAITING = 'waiting'
  1845. #
  1846. # RUNNING = 'running'
  1847. # END = "end" # 结束运行的状态
  1848. # FINISHED = 'finished'
  1849. # WAIT = 'waitPay'
  1850. # TIMEOUT = 'timeout'
  1851. # FAILURE = 'failure'
  1852. # UNKNOWN = 'unknown'
  1853. #
  1854. # # TODO: 以下三个状态蓝牙使用过, 后续整改
  1855. # INIT = 'init'
  1856. # CHARGED = 'Charged'
  1857. # REFUND = 'Refund'
  1858. #
  1859. # orderNo = StringField(verbose_name = "订单号", default = "")
  1860. # ownerId = StringField(verbose_name = '经销商ID', default = "")
  1861. # price = MonetaryField(verbose_name = "消耗的钱(硬币)", default = RMB('0.00'))
  1862. #
  1863. # #: 在部分场合,设备会失败启动,但这时应该添加该字段为False方便追溯
  1864. # isNormal = BooleanField(verbose_name = "设备是否正常启动", default = True)
  1865. # errorDesc = StringField(verbose_name = u"错误描述", default = '')
  1866. #
  1867. # remarks = StringField(verbose_name = u"备注(默认值不能为None, 否则在使用in的时候出错)", default = '')
  1868. #
  1869. # dateTimeCompletion = DateTimeField(verbose_name = '结单时间', default = None)
  1870. #
  1871. # sequanceNo = StringField(verbose_name = u'消费的流水号,用于和设备上的订单关联', default = None)
  1872. #
  1873. # status = StringField(verbose_name = u'状态', default = '')
  1874. #
  1875. # startKey = StringField(verbose_name = u'启动key')
  1876. #
  1877. # attachParas = DictField(verbose_name = u'用户选择的额外参数', default = {}) # 比如端口、电池类型等信息
  1878. #
  1879. # startTime = DateTimeField(verbose_name = u'设备启动时间', default = None)
  1880. # finishedTime = DateTimeField(verbose_name = u'设备结束时间', default = None)
  1881. #
  1882. # servicedInfo = DictField(verbose_name = u'服务内容', default = {})
  1883. # desc = StringField(verbose_name = u'描述', default = None)
  1884. #
  1885. # feedbackId = ObjectIdField(verbose_name = u'用户反馈ID', default = None)
  1886. #
  1887. # refunded = BooleanField(verbose_name = u'是否已经处理完退费', default = None)
  1888. #
  1889. # aggInfo = DictField(verbose_name = u'聚合信息', default = {})
  1890. #
  1891. # rechargeRcdId = StringField(verbose_name = u'如果是快捷支付,可以关联充值订单(比如昌原,没有金币的概念,需要查询每单的花费)', default = None)
  1892. #
  1893. # # {via: coin/card/virtualCard/free/netPay itemId: 相应的id money: 现金金额 coins: 金币支付金额}
  1894. # payInfo = ListField(verbose_name = u'支付的相关途径', default = [])
  1895. # paymentInfo = DictField(verbose_name = u'支付的相关途径', default = {})
  1896. #
  1897. # package = DictField(verbose_name = u'套餐', default = {})
  1898. #
  1899. # # role: 'main', list<id>; rule: 'sub', id
  1900. # association = DictField(verbose_name = u'关联单', default = {})
  1901. #
  1902. # # 以下字段不在使用
  1903. # leftBalance = StringField(verbose_name = u"心跳机制返回的剩余金额", default = None)
  1904. # rechargeRcdIds = ListField(verbose_name = u'现金充值的订单id', default = None)
  1905. #
  1906. # search_fields = ('openId', 'devNo', 'orderNo', 'remarks', 'address', 'groupName', 'logicalCode')
  1907. #
  1908. # _shard_key = ('ownerId', 'dateTimeAdded')
  1909. #
  1910. # _origin_meta = {
  1911. # "collection": "ConsumeRecord",
  1912. # "db_alias": "default"
  1913. # }
  1914. #
  1915. # meta = _origin_meta
  1916. #
  1917. # # 订单的5种基础状态 后续有需要可以添加
  1918. # states = ["created", "wait", "running", "finished", "waitPay", "end"]
  1919. #
  1920. # # 两种支付流程各个状态的切换
  1921. # # created ---> start ---> end ---> waitPay ---> finished
  1922. # # created ---> waitPay ---> start ---> end ---> finished
  1923. #
  1924. # def __init__(self, *args, **kwargs):
  1925. # # super(ConsumeRecord, self).__init__(*args, **kwargs)
  1926. # Searchable.__init__(self, *args, **kwargs)
  1927. #
  1928. # # 这个目前只有后付费的订单支持这个 做了一个简易的开关
  1929. # # mongoDB 使用save的时候 会有created 以及 update的区别 已知的情况是 在save_created的时候,如果运行了_add_state_machine 会造成数据插入异常
  1930. # if self.id and self.devTypeCode and self.status in ConsumeRecord.states:
  1931. # self._add_state_machine()
  1932. #
  1933. # def __str__(self):
  1934. # return '{}<id={} orderNo={}>'.format(self.__class__.__name__, str(self.id), self.orderNo)
  1935. #
  1936. # @property
  1937. # def owner(self):
  1938. # from apps.web.dealer.models import Dealer
  1939. #
  1940. # _attr_name = '__my_owner__'
  1941. #
  1942. # if hasattr(self, _attr_name):
  1943. # return getattr(self, _attr_name)
  1944. # else:
  1945. # if not self.ownerId:
  1946. # return None
  1947. # else:
  1948. # dealer = Dealer.objects(id = self.ownerId).first()
  1949. # setattr(self, _attr_name, dealer)
  1950. # return dealer
  1951. #
  1952. # def _add_state_machine(self):
  1953. # """
  1954. # 为订单对象创建状态机 并初始化状态机
  1955. # :return:
  1956. # """
  1957. # self._machine = Machine(model = self, states = ConsumeRecord.states, initial = self.status,
  1958. # after_state_change = ["_after_state_change"], queued = True, auto_transitions = False)
  1959. #
  1960. # # 订单创建之后 需要将订单转换到下一个状态,订单的转换状态是根据订单是否正常来切换的
  1961. # self._machine.add_transition("c_to_s", "created", "running", conditions = ['isNormal'])
  1962. # self._machine.add_transition("c_to_s", "created", "finished", unless = ["isNormal"])
  1963. #
  1964. # # 订单运行状态到订单停止运行状态
  1965. # self._machine.add_transition("s_to_e", "running", "end", after = ["_pay_coins", "e_to_f"])
  1966. # # 订单虚拟货币支付, 支付成功之后将订单状态切换为结束,结束之后需要有数据汇总的操作 支付不成功智能等待用户主动现金支付
  1967. # self._machine.add_transition("e_to_f", "end", "finished", conditions = ["_isPaid"],
  1968. # after = ["_stats_info", "_do_dev_callback"])
  1969. # self._machine.add_transition("e_to_e", "end", "end", unless = ["_isPaid"])
  1970. # self._machine.add_transition("e_to_p", "end", "waitPay", after = ["_add_pay_time_point"])
  1971. #
  1972. # # 订单支后,轮询的回调函数,一旦轮询到订单支付成功, 则将状态改变 由于不知道end状态是会首先变成paying再变成finished 还是直接由end变为finished, 接受的初始状态实体应该是一个列表
  1973. # self._machine.add_transition("p_to_f", "waitPay", "finished", conditions = ["_isRecharge", "_isNotPaid"],
  1974. # after = ["_pay_cash", "_do_dev_callback"])
  1975. # self._machine.add_transition("p_to_f", "waitPay", "end", unless = ["_isRecharge", "_isPaid"],
  1976. # after = ["_clear_recharge"])
  1977. #
  1978. # def _after_state_change(self):
  1979. # """
  1980. # 订单使用状态机切换状态之后,将订单的status相应修改,并且重载一下
  1981. # :return:
  1982. # """
  1983. # self.update(status = self.state)
  1984. # self.reload()
  1985. #
  1986. # def _pay_coins(self):
  1987. # """
  1988. # 订单运行结束之后,直接尝试使用虚拟货币对订单进行支付
  1989. # :return:
  1990. # """
  1991. #
  1992. # # 已经支付了或者已经有充值订单相关联
  1993. # if self._isPaid or self._isRecharge:
  1994. # return
  1995. #
  1996. # # 尝试使用包月卡支付
  1997. # dev = Device.get_dev(self.devNo)
  1998. # package = dev.get("washConfig", dict()).get(self.attachParas.get("packageId", "1"))
  1999. #
  2000. # monthlyPackage = MonthlyPackage.get_enable_one(self.openId, self.groupId)
  2001. # if monthlyPackage and monthlyPackage.is_suit_package(package):
  2002. # monthlyPackage.deduct(self)
  2003. # self.paymentInfo = { # zjl [] -> {}
  2004. # "via": "monthlyPackage",
  2005. # "itemId": str(monthlyPackage.id),
  2006. # "money": RMB(0).mongo_amount,
  2007. # "coins": VirtualCoin(0).mongo_amount
  2008. # } # zjl [] -> {}
  2009. # self.save()
  2010. # return
  2011. #
  2012. # dev = Device.get_dev(self.devNo) # type: DeviceDict
  2013. # group = Group.get_group(self.groupId)
  2014. # dealer = dev.owner # type: Dealer
  2015. #
  2016. # devTypeCode = dev.get("devType", dict()).get("code", "")
  2017. #
  2018. # # 尝试使用虚拟卡支付
  2019. # # package = {"unit": u"币", "count": self.coin}
  2020. # vCards = UserVirtualCard.get_user_cards(self.openId, self.groupId, self.ownerId)
  2021. # for vCard in vCards:
  2022. # if vCard.support_dev_type(devTypeCode) and vCard.can_use_today(package):
  2023. # # TODO zjl 先简陋处理一下 后续将其更换位置
  2024. # from apps.web.user.utils import vCard_pay_coin
  2025. # vCard_pay_coin(self, vCard, dev, group, package, self.attachParas) # type: VCardConsumeRecord
  2026. # return
  2027. #
  2028. # user = MyUser.objects(openId = self.openId, groupId = self.groupId).first() # type: MyUser
  2029. # balance = user.calc_currency_balance(dealer, group)
  2030. #
  2031. # if balance >= VirtualCoin(self.coin):
  2032. # # 金币足够支付的情况下 直接使用金币支付
  2033. # from apps.web.user.utils import user_pay_coin
  2034. # user_pay_coin(self.openId, self.devNo, self.groupId, self.ownerId, self.coin)
  2035. # self.paymentInfo = {
  2036. # "via": "coin",
  2037. # "itemId": "",
  2038. # "money": RMB(0).mongo_amount,
  2039. # "coins": VirtualCoin(self.coin).mongo_amount
  2040. # }
  2041. # self.save()
  2042. #
  2043. # def _clear_recharge(self):
  2044. # self.rechargeRcdId = ""
  2045. # self.save()
  2046. #
  2047. # def _pay_cash(self):
  2048. # # 这一步的时候实际上已经支付成功了,所需要做的步骤就是将支付的信息加载进 payInfo 字段中
  2049. # self.paymentInfo = {
  2050. # "via": "cash",
  2051. # "itemId": self.rechargeRcdId,
  2052. # "money": RMB(self.money).mongo_amount,
  2053. # "coins": VirtualCoin(0).mongo_amount
  2054. # }
  2055. # self.save()
  2056. #
  2057. # @property
  2058. # def _isPaid(self):
  2059. # """
  2060. # 检查用户是否已经为此订单付了钱 无论是此订单关联的recharge 还是使用金币支付 或者是使用虚拟卡支付
  2061. # :return:
  2062. # """
  2063. # return any([self.payInfo, self.paymentInfo])
  2064. #
  2065. # @property
  2066. # def _isNotPaid(self):
  2067. # return not self._isPaid
  2068. #
  2069. # @property
  2070. # def _isRecharge(self):
  2071. # """
  2072. # 检测订单是否已经与充值订单相关联
  2073. # :return:
  2074. # """
  2075. # rechargeRcdId = self.recharge_record_id
  2076. #
  2077. # if not rechargeRcdId:
  2078. # return False
  2079. #
  2080. # try:
  2081. # from apps.web.common.proxy import ClientRechargeModelProxy
  2082. # record = ClientRechargeModelProxy.get_one(
  2083. # shard_filter = {'ownerId': self.ownerId},
  2084. # id = str(rechargeRcdId)) # type: RechargeRecord
  2085. # except DoesNotExist:
  2086. # return False
  2087. # except Exception as e:
  2088. # logger.exception(e)
  2089. # return False
  2090. #
  2091. # if record.result == RechargeRecord.PayResult.SUCCESS:
  2092. # # 下面还要用的,防止重复查询
  2093. # setattr(self, "_rechargeRcd", record)
  2094. # return True
  2095. # else:
  2096. # return False
  2097. #
  2098. # def _do_dev_callback(self):
  2099. # dev = Device.get_dev(self.devNo)
  2100. # box = ActionDeviceBuilder.create_action_device(dev)
  2101. # if box.isHaveCallback():
  2102. # try:
  2103. # box.do_callback(self)
  2104. # except Exception:
  2105. # self.attachParas.update({"callbackResult": False})
  2106. # self.save()
  2107. # self.attachParas.update({"callbackResult": True})
  2108. # self.save()
  2109. #
  2110. # def _add_pay_time_point(self):
  2111. #
  2112. # self.attachParas['payTimePoint'] = datetime.datetime.now()
  2113. # self.save()
  2114. #
  2115. # @property
  2116. # def is_from_api(self):
  2117. # return False
  2118. #
  2119. # @property
  2120. # def is_on_point(self):
  2121. # return False
  2122. #
  2123. # @property
  2124. # def is_from_user(self):
  2125. # return True
  2126. #
  2127. # @property
  2128. # def used_port(self):
  2129. # port = self.attachParas['chargeIndex'] if (
  2130. # self.attachParas and self.attachParas.has_key('chargeIndex')
  2131. # and self.attachParas['chargeIndex']) else -1
  2132. # try:
  2133. # return int(port)
  2134. # except:
  2135. # return port
  2136. #
  2137. # @property
  2138. # def created_date(self):
  2139. # return self.dateTimeAdded
  2140. #
  2141. # @property
  2142. # def completion_date(self):
  2143. # if self.dateTimeCompletion:
  2144. # return self.dateTimeCompletion
  2145. # else:
  2146. # return self.finishedTime
  2147. #
  2148. # @property
  2149. # def device_start_time(self):
  2150. # if self.startTime:
  2151. # return self.startTime
  2152. # else:
  2153. # return self.dateTimeAdded
  2154. #
  2155. # @property
  2156. # def device_finished_time(self):
  2157. # if self.finishedTime:
  2158. # return self.finishedTime
  2159. # else:
  2160. # return ''
  2161. #
  2162. # @classmethod
  2163. # def new_one(cls, order_no, user, device, group, package, attach_paras, pay_info = None,
  2164. # start_key = None, sequence_no = None):
  2165. # # type: (str, MyUser, DeviceDict, GroupDict, Dict, Dict, Dict, str, str)->ConsumeRecord
  2166. #
  2167. # order = cls(
  2168. # orderNo = order_no,
  2169. # time = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
  2170. # openId = user.openId,
  2171. # nickname = user.nickname,
  2172. # coin = VirtualCoin(0),
  2173. # money = RMB(0),
  2174. # devNo = device.devNo,
  2175. # logicalCode = device.logicalCode,
  2176. # devTypeCode = device.devTypeCode,
  2177. # devTypeName = device.devTypeName,
  2178. # groupId = device.groupId,
  2179. # ownerId = device.ownerId,
  2180. # address = group.address,
  2181. # groupNumber = device.groupNumber,
  2182. # groupName = group.groupName,
  2183. # startKey = start_key,
  2184. # isNormal = False,
  2185. # errorDesc = '',
  2186. # sequanceNo = sequence_no,
  2187. # attachParas = attach_paras,
  2188. # status = cls.Status.CREATED,
  2189. # paymentInfo = pay_info,
  2190. # package = package)
  2191. #
  2192. # remarks = None
  2193. # money = RMB(0)
  2194. # coins = VirtualCoin(0)
  2195. #
  2196. # if pay_info:
  2197. # if pay_info['via'] == 'free':
  2198. # remarks = u'免费'
  2199. # elif pay_info['via'] == 'virtualCard':
  2200. # remarks = u'虚拟卡券'
  2201. # elif pay_info['via'] == 'coins':
  2202. # coins = pay_info['coins']
  2203. # remarks = u'余额支付'
  2204. # elif pay_info['via'] == 'card':
  2205. # coins = pay_info['coins']
  2206. # money = pay_info['coins']
  2207. # remarks = u'刷卡消费'
  2208. # else:
  2209. # money = pay_info['money']
  2210. # coins = pay_info['coins']
  2211. # remarks = u'网络支付'
  2212. #
  2213. # order.paymentInfo = pay_info
  2214. # order.remarks = remarks
  2215. # order.money = money
  2216. # order.coin = coins
  2217. #
  2218. # order.save()
  2219. #
  2220. # return order
  2221. #
  2222. # def update_service_info(self, consumeDict, isFinished=True):
  2223. # # type:(dict, bool)->None
  2224. #
  2225. # try:
  2226. # trans_consume_dict = {}
  2227. #
  2228. # for key, value in consumeDict.iteritems():
  2229. # if value is None:
  2230. # logger.debug('{} is none.'.format(key))
  2231. # continue
  2232. #
  2233. # if hasattr(value, 'mongo_amount'):
  2234. # trans_consume_dict[key] = value.mongo_amount
  2235. # else:
  2236. # trans_consume_dict[key] = value
  2237. #
  2238. # self.servicedInfo.update(trans_consume_dict)
  2239. # self.save()
  2240. #
  2241. # if not isFinished:
  2242. # return
  2243. #
  2244. # status = self.update_agg_info(trans_consume_dict)
  2245. # if status:
  2246. # record_consumption_stats(self)
  2247. # else:
  2248. # logger.error(
  2249. # '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (self,))
  2250. # except Exception, e:
  2251. # logger.exception('update agg info error=%s' % e)
  2252. #
  2253. # def update_for_end(self, serviceInfo = None, finishedTime=datetime.datetime.now()):
  2254. # """
  2255. # 订单切换到设备运行结束的状态
  2256. # :param serviceInfo:
  2257. # :param finishedTime
  2258. # :return:
  2259. # """
  2260. #
  2261. # self.status = self.Status.FINISHED
  2262. # self.finishedTime = to_datetime(finishedTime)
  2263. #
  2264. # serviceInfo = serviceInfo or dict()
  2265. #
  2266. # self.update_service_info(consumeDict=serviceInfo)
  2267. #
  2268. # @property
  2269. # def receiptDesc(self):
  2270. # rv = {
  2271. # 'orderNo': self.orderNo,
  2272. # 'createdTime': self.dateTimeAdded,
  2273. # 'payment': u'{} 金币'.format(str(VirtualCoin(self.coin)))
  2274. # }
  2275. #
  2276. # if self.paymentInfo:
  2277. # if self.paymentInfo['via'] == 'free':
  2278. # rv.update({'payment': u'本次免费'})
  2279. # return rv
  2280. # elif self.paymentInfo['via'] == 'cash':
  2281. # rv.update({'payment': u'{} 元'.format(self.paymentInfo['money'])})
  2282. # return rv
  2283. # elif self.paymentInfo['via'] == 'virtualCard':
  2284. # rv.update({'payment': u'虚拟卡抵扣'})
  2285. # return rv
  2286. # elif self.paymentInfo['via'] == 'yuchuanCard':
  2287. # rv.update({'payment': u'{} 元 宇川一卡通'.format(self.paymentInfo['money'])})
  2288. # return rv
  2289. #
  2290. # if self.remarks == u'虚拟卡消费':
  2291. # rv.update({'payment': u'虚拟卡抵扣'})
  2292. # return rv
  2293. #
  2294. # if self.remarks == u'包月卡消费':
  2295. # rv.update({'payment': u'包月卡抵扣'})
  2296. # return rv
  2297. #
  2298. # if self.remarks == 'YuChuanCard':
  2299. # rv.update({'payment': u'{} 元 宇川一卡通'.format(self.paymentInfo['money'])})
  2300. # return rv
  2301. #
  2302. # return rv
  2303. #
  2304. # def _stats_info(self):
  2305. # """
  2306. # 订单切换为最终完成状态之后 将订单的信息上报到数据中心进行统计
  2307. # :return:
  2308. # """
  2309. # duration = self._duration
  2310. # if duration:
  2311. # agg_info = {
  2312. # DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  2313. # DEALER_CONSUMPTION_AGG_KIND.COIN: self.coin
  2314. # }
  2315. # else:
  2316. # agg_info = {
  2317. # DEALER_CONSUMPTION_AGG_KIND.COIN: self.coin
  2318. # }
  2319. #
  2320. # status = self.update_agg_info(agg_info)
  2321. # if status:
  2322. # record_consumption_stats(self)
  2323. #
  2324. # @property
  2325. # def _duration(self):
  2326. # """
  2327. # 计算订单的持续时长
  2328. # :return:
  2329. # """
  2330. # if self.state != "finished" or not self.finishedTime:
  2331. # return 0
  2332. #
  2333. # if 'duration' in self.servicedInfo and isinstance(self.servicedInfo['duration'], (float, int)):
  2334. # duration = self.servicedInfo['duration']
  2335. #
  2336. # else:
  2337. # # 计算时长不需要很精确,直接使用round算了
  2338. # duration = round((self.finishedTime - self.dateTimeAdded).total_seconds() / 60.0)
  2339. #
  2340. # return duration
  2341. #
  2342. # @property
  2343. # def virtual_card_id(self):
  2344. # if not self.paymentInfo:
  2345. # return None
  2346. #
  2347. # via = self.paymentInfo.get('via', None)
  2348. #
  2349. # if via == 'virtualCard':
  2350. # return self.paymentInfo['itemId']
  2351. # else:
  2352. # return None
  2353. #
  2354. # @property
  2355. # def recharge_record_id(self):
  2356. # if self.rechargeRcdId and str(self.rechargeRcdId) != 'None':
  2357. # return str(self.rechargeRcdId)
  2358. #
  2359. # if not self.paymentInfo:
  2360. # return None
  2361. #
  2362. # via = self.paymentInfo.get('via', None)
  2363. # if via in ['netPay', 'cash', 'coins']:
  2364. # if 'itemId' in self.paymentInfo and self.paymentInfo['itemId']:
  2365. # return str(self.paymentInfo['itemId'])
  2366. # else:
  2367. # return None
  2368. # else:
  2369. # return None
  2370. #
  2371. # @property
  2372. # def payTypeInfo(self):
  2373. # cardId = self.attachParas.get("cardId")
  2374. # vCardId = self.attachParas.get("vCardId")
  2375. # data = dict()
  2376. #
  2377. # if cardId:
  2378. # try:
  2379. # cardNo = Card.objects.get(id = cardId).cardNo
  2380. # except Exception as e:
  2381. # logger.exception(e)
  2382. # else:
  2383. # data.update({"cardId": cardId, "cardNo": cardNo})
  2384. #
  2385. # if vCardId:
  2386. # try:
  2387. # vCardNo = UserVirtualCard.objects.get(id = vCardId).cardNo
  2388. # except Exception as e:
  2389. # logger.exception(e)
  2390. # else:
  2391. # data.update({"vCardId": vCardId, "vCardNo": vCardNo})
  2392. #
  2393. # return data
  2394. #
  2395. # @staticmethod
  2396. # def make_no(*args, **kwargs):
  2397. # timestamp = generate_timestamp_ex()
  2398. # random_int = random.randint(1, 1000)
  2399. # return "%d%03d" % (timestamp, random_int)
  2400. #
  2401. # @staticmethod
  2402. # def make_order_no(identifier, sub_type):
  2403. # return OrderNoMaker.make_order_no_32(identifier, OrderMainType.CONSUME, sub_type)
  2404. #
  2405. # @property
  2406. # def consume_type(self):
  2407. # if self.remarks:
  2408. # if u'刷卡' in self.remarks:
  2409. # return 'card'
  2410. # elif u'虚拟卡' in self.remarks:
  2411. # return 'mobile_vcard'
  2412. # elif u'投币或者刷卡消费' in self.remarks:
  2413. # return 'offline'
  2414. # elif u'包月卡' in self.remarks:
  2415. # return 'monthlyPackage'
  2416. #
  2417. # if self.recharge_record_id:
  2418. # return 'cash'
  2419. # else:
  2420. # return 'balance'
  2421. #
  2422. # def to_user_detail(self):
  2423. # """
  2424. # 消费记录对于用户端显示的详细信息
  2425. # :return:
  2426. # """
  2427. #
  2428. # data = {
  2429. # 'id': str(self.id),
  2430. #
  2431. # 'consumeType': self.consume_type,
  2432. #
  2433. # 'orderNo': self.orderNo,
  2434. #
  2435. # 'ownerId': self.ownerId,
  2436. #
  2437. # 'money': self.money,
  2438. #
  2439. # 'coins': self.coin,
  2440. #
  2441. # 'createdTime': self.created_date,
  2442. # 'completionTime': self.completion_date,
  2443. #
  2444. # 'startResult': 'success' if self.isNormal else 'failed',
  2445. # 'errorDesc': self.errorDesc,
  2446. #
  2447. # 'deviceStatTime': self.device_start_time,
  2448. # 'deviceFinishedTime': self.device_finished_time,
  2449. #
  2450. # 'reason': self.servicedInfo.get("reason", "")
  2451. # }
  2452. #
  2453. # data.update(self.device_identity_info)
  2454. #
  2455. # data.update(self.refund_info)
  2456. #
  2457. # if self.recharge_record_id:
  2458. # recharge_record = RechargeRecord.objects(id = self.recharge_record_id).first() # type: RechargeRecord
  2459. # if recharge_record:
  2460. # data.update({
  2461. # 'rechargeRcdId': str(recharge_record.id),
  2462. # 'rechargeTradeNo': recharge_record.orderNo,
  2463. # })
  2464. #
  2465. # # 兼容之前的订单 凡是没有订单状态机的订单,一律视为 之前的订单
  2466. # # 简陋 的实现以下订单状态改变
  2467. # if self.devTypeCode in [
  2468. # Const.DEVICE_TYPE_CODE_CABINET,
  2469. # Const.DEVICE_TYPE_CODE_CHARGE_XIAOKEDOU,
  2470. # Const.DEVICE_TYPE_CODE_CABINET_NEW,
  2471. # Const.DEVICE_TYPE_CODE_CHARGE_WEIFULE_CAR,
  2472. # Const.DEVICE_TYPE_CODE_CAR_WEIFULE_CHARGING_DOUB,
  2473. # Const.DEVICE_TYPE_CODE_CAR_WEIFULE_21KW,
  2474. # Const.DEVICE_TYPE_CODE_CAR_WEIFILE_HOME_JFPG,
  2475. # Const.DEVICE_TYPE_CODE_CAR_WEIFILE_HOME_DOUB_JFPG,
  2476. # ] + support_policy_device + support_policy_weifule:
  2477. # self.charge_recharge_status()
  2478. # try:
  2479. # data.update({"orderStatus": self.state})
  2480. # except Exception as e:
  2481. # logger.exception(e)
  2482. #
  2483. # if self.servicedInfo:
  2484. # self.servicedInfo.pop('refundedMoney', None)
  2485. # self.servicedInfo.pop('refundedCash', None)
  2486. # data.update(self.servicedInfo)
  2487. #
  2488. # data.update(self.refund_info)
  2489. #
  2490. # return data
  2491. #
  2492. # @property
  2493. # def summary(self):
  2494. # return {
  2495. # 'id': str(self.id),
  2496. #
  2497. # 'consumeType': self.consume_type,
  2498. #
  2499. # 'ownerId': self.ownerId,
  2500. #
  2501. # 'logicalCode': self.logicalCode,
  2502. #
  2503. # 'groupName': self.groupName,
  2504. # 'devTypeName': self.dev_type_name,
  2505. #
  2506. # 'money': self.money,
  2507. # 'coins': self.coin,
  2508. #
  2509. # 'createdTime': self.dateTimeAdded.strftime(Const.DATETIME_FMT),
  2510. # }
  2511. #
  2512. # def to_detail(self, source = None, hides = []):
  2513. # # type: (Optional[str], Optional[list])->dict
  2514. #
  2515. # data = {
  2516. # 'id': str(self.id),
  2517. #
  2518. # 'orderNo': self.orderNo,
  2519. #
  2520. # 'createdTime': self.created_date,
  2521. # 'completionTime': self.completion_date,
  2522. #
  2523. # 'deviceStatTime': self.device_start_time,
  2524. # 'deviceFinishedTime': self.device_finished_time,
  2525. #
  2526. # 'orderAmount': self.coin,
  2527. #
  2528. # 'amount': self.coin,
  2529. # 'coins': self.coin,
  2530. #
  2531. # 'openId': self.openId,
  2532. # 'groupId': self.groupId,
  2533. # 'userNickname': u'用户' if not self.nickname else self.nickname,
  2534. #
  2535. # 'ownerId': self.ownerId,
  2536. #
  2537. # 'devNo': self.devNo,
  2538. # 'logicalCode': self.logicalCode,
  2539. # 'groupName': self.groupName,
  2540. # 'address': self.address,
  2541. # 'groupNumber': self.groupNumber,
  2542. # 'devType': self.dev_type_name, # 兼容
  2543. # 'devTypeName': self.dev_type_name,
  2544. #
  2545. # 'remarks': self.remarks,
  2546. #
  2547. # 'startResult': 'success' if self.isNormal else 'failed',
  2548. #
  2549. # 'errorDesc': self.errorDesc,
  2550. #
  2551. # 'servicedInfo': [u'%s: %s'.encode('utf-8')
  2552. # % (GLOSSARY_TRANSLATION.get(k, k), v)
  2553. # for k, v in self.servicedInfo.iteritems()
  2554. # if (k not in self.aggInfo and k in GLOSSARY_TRANSLATION)],
  2555. #
  2556. # 'aggInfo': self.consumption_stats(source = source, hides = hides),
  2557. #
  2558. # # 以下字段和以前做兼容
  2559. # 'finishedTime': self.device_finished_time,
  2560. # 'port': self.used_port
  2561. # }
  2562. #
  2563. # if self.rechargeRcdId:
  2564. # recharge_record = RechargeRecord.objects(id = self.rechargeRcdId).first() # type: RechargeRecord
  2565. # if recharge_record:
  2566. # data.update({
  2567. # 'gateway': recharge_record.gateway,
  2568. # 'rechargeRcdId': str(recharge_record.id),
  2569. # 'rechargeTradeNo': recharge_record.orderNo,
  2570. # 'gatewayTradeNo': recharge_record.wxOrderNo # 兼容
  2571. # })
  2572. #
  2573. # if self.redpackId:
  2574. # redPack = Redpack.get_one(self.redpackId)
  2575. # if redPack:
  2576. # package = self.package
  2577. # coins = package.get('coins')
  2578. # price = package.get('price')
  2579. # redpackMoney = redPack['money']
  2580. #
  2581. # if RMB(redpackMoney) >= RMB(price): # 消费中红包抵扣 做一个保护
  2582. # redpackAmount = coins
  2583. # else:
  2584. # redpackAmount = round(float(RMB(redpackMoney)) / price * coins, 2)
  2585. #
  2586. # data.update({'redpackAmount': redpackAmount})
  2587. #
  2588. # if self.devTypeCode in ["1008031", "100803"] and self.status:
  2589. # data.update({"status": self.status})
  2590. #
  2591. # payTypeInfo = self.payTypeInfo
  2592. # if payTypeInfo:
  2593. # data.update(payTypeInfo)
  2594. #
  2595. # return data
  2596. #
  2597. # def charge_recharge_status(self):
  2598. # if not hasattr(self, "is_waitPay") or not self.is_waitPay():
  2599. # return
  2600. #
  2601. # if self.recharge_record_id:
  2602. # from apps.web.common.proxy import ClientRechargeModelProxy
  2603. #
  2604. # rechargeRecord = ClientRechargeModelProxy.get_one(
  2605. # shard_filter = {'ownerId': self.ownerId},
  2606. # id = self.recharge_record_id) # type: RechargeRecord
  2607. # if int((datetime.datetime.now() - rechargeRecord.dateTimeAdded).total_seconds()) > 60 * 3:
  2608. # self.p_to_f()
  2609. #
  2610. # def consumption_stats(self, source = None, hides = []):
  2611. # # type:(Optional[str], Optional[list])->list
  2612. # return translate_consumption_stats(self.aggInfo.iteritems(), source, hides)
  2613. #
  2614. # def to_dict(self, source = None, hides = []):
  2615. # # type: (Optional[str], Optional[list])->Dict[str, Union[AnyStr, float]]
  2616. #
  2617. # title = u'设备{logicalCode}消费 {agg_info} '.format(logicalCode = self.logicalCode,
  2618. # agg_info = '-'.join(self.consumption_stats(source, hides)))
  2619. # return {
  2620. # 'id': str(self.id),
  2621. # 'title': title,
  2622. # 'amount': self.coin,
  2623. # 'createdTime': self.created_date,
  2624. # 'source': source,
  2625. # 'ownerId': self.ownerId,
  2626. # 'startResult': 'success' if self.isNormal else 'failed',
  2627. #
  2628. # 'logicalCode': self.logicalCode,
  2629. # 'devTypeName': self.dev_type_name,
  2630. # 'userNickname': self.nickname
  2631. # }
  2632. #
  2633. # def update_agg_info(self, mapping):
  2634. # # type: (dict)->bool
  2635. # """
  2636. #
  2637. # :param mapping:
  2638. # :return:
  2639. # """
  2640. # try:
  2641. # trans_consume_dict = {}
  2642. # for kind, value in mapping.iteritems():
  2643. # if kind not in DEALER_CONSUMPTION_AGG_KIND.choices():
  2644. # logger.debug('{} is not consume stat kind.'.format(kind))
  2645. # continue
  2646. #
  2647. # if value is None:
  2648. # logger.debug('{} is none.'.format(kind))
  2649. # continue
  2650. #
  2651. # if hasattr(value, 'mongo_amount'):
  2652. # trans_consume_dict[kind] = value.mongo_amount
  2653. # else:
  2654. # trans_consume_dict[kind] = value
  2655. #
  2656. # self.aggInfo.update(trans_consume_dict)
  2657. # self.save()
  2658. #
  2659. # except Exception as e:
  2660. # logger.exception(e)
  2661. # return False
  2662. #
  2663. # return True
  2664. #
  2665. # @classmethod
  2666. # def get_consumed_records(cls, **kwargs):
  2667. # return cls.objects(isNormal = True, **kwargs).order_by('-dateTimeAdded')
  2668. #
  2669. # @property
  2670. # def is_temp_package(self):
  2671. # if 'isTempPackage' in self.attachParas:
  2672. # return self.attachParas['isTempPackage']
  2673. #
  2674. # if 'isTempPackage' in self.package:
  2675. # return self.package['isTempPackage']
  2676. #
  2677. # return False
  2678. #
  2679. # @property
  2680. # def is_free(self):
  2681. # if 'isFree' in self.attachParas:
  2682. # return self.attachParas['isFree']
  2683. #
  2684. # if 'isFree' in self.package:
  2685. # return self.package['isFree']
  2686. #
  2687. # return False
  2688. #
  2689. # @property
  2690. # def my_package(self):
  2691. # return PackageDict(self.package)
  2692. #
  2693. # @property
  2694. # def statistic_type(self):
  2695. # return "consumption"
  2696. #
  2697. # @property
  2698. # def dealerIds(self):
  2699. # return [ObjectId(self.ownerId)] + [ObjectId(_['id']) for _ in
  2700. # Group.get_group(self.groupId)['partnerDict'].values()]
  2701. #
  2702. # def get_statistic_update_info(self, amount = None):
  2703. # """
  2704. # 获取 更新的数据
  2705. # :param amount: 设置的金币的金额
  2706. # :return:
  2707. # """
  2708. # kindMap = self.aggInfo or dict()
  2709. #
  2710. # kindMap.setdefault("coin", VirtualCoin(amount or self.coin))
  2711. # hour = self.dateTimeAdded.hour
  2712. #
  2713. # updateOrInsertData = {
  2714. # "add_to_set__origin__{}".format(self.statistic_type): self.id,
  2715. # "inc__daily__totalConsumptionCount": 1
  2716. # }
  2717. #
  2718. # for _kind, _value in kindMap.items():
  2719. # updateOrInsertData["inc__daily__{}__{}".format(self.statistic_type, _kind)] = \
  2720. # to_quantity(_kind, _value).mongo_amount
  2721. # updateOrInsertData["inc__hourly__{}__{}__{}".format(hour, self.statistic_type, _kind)] = \
  2722. # to_quantity(_kind, _value).mongo_amount
  2723. #
  2724. # return updateOrInsertData
  2725. #
  2726. # def deail_link(self, device):
  2727. # # type:(DeviceDict)->str
  2728. #
  2729. # if device.channelType == DeviceChannelType.Channel_BT:
  2730. # if device.deviceAdapter.support_count_down():
  2731. # return concat_bt_count_down_page_url(l = self.logicalCode, port = self.used_port)
  2732. # else:
  2733. # if device.devTypeCode in ['1002121', '1002122']:
  2734. # return concat_front_end_url(uri = '/user/index.html#/user/deviceStatus')
  2735. # else:
  2736. # if device.deviceAdapter.support_count_down(self.openId, self.used_port):
  2737. # return concat_count_down_page_url(devNo = self.devNo, port = self.used_port)
  2738. # else:
  2739. # return concat_front_end_url(uri = '/user/index.html#/user/consumeDetail?id={}'.format(str(self.id)))
  2740. #
  2741. # def update_paid(self, **kwargs):
  2742. # # type: (dict)->None
  2743. #
  2744. # if not self.paymentInfo:
  2745. # logger.debug('consume record<id={}> is old version.'.format(str(self.id)))
  2746. # return
  2747. #
  2748. # self.paymentInfo['payTime'] = datetime.datetime.now()
  2749. #
  2750. # vCardConsumeRecord = kwargs.get('vCardConsumeRecord', None)
  2751. # if vCardConsumeRecord:
  2752. # assert self.paymentInfo['via'] == 'virtualCard', 'is not virtual card pay type.'
  2753. #
  2754. # self.paymentInfo['vCardConsumeRcdId'] = str(vCardConsumeRecord.id)
  2755. # self.paymentInfo['vCardConsumeData'] = {
  2756. # 'consumeData': vCardConsumeRecord.consumeData,
  2757. # 'consumeDayData': vCardConsumeRecord.consumeDayData
  2758. # }
  2759. #
  2760. # self.save()
  2761. #
  2762. # def update_failure(self, errorDesc):
  2763. # self.isNormal = False
  2764. # self.errorDesc = errorDesc
  2765. # if not self.dateTimeCompletion:
  2766. # self.dateTimeCompletion = datetime.datetime.now()
  2767. #
  2768. # self.save()
  2769. #
  2770. # @property
  2771. # def user(self):
  2772. # return MyUser.objects.filter(openId = self.openId, groupId = self.groupId).first()
  2773. #
  2774. # @property
  2775. # def redpackId(self):
  2776. # return self.attachParas.get('redpackId', "")
  2777. #
  2778. # @property
  2779. # def insId(self):
  2780. # return self.attachParas.get("insId", "")
  2781. #
  2782. # @property
  2783. # def monthlyPackageId(self):
  2784. # return self.attachParas.get('monthlyPackageId', "")
  2785. #
  2786. # @property
  2787. # def refund_info(self):
  2788. # rv = {
  2789. # 'isRefund': False
  2790. # }
  2791. #
  2792. # if self.isNormal:
  2793. # if self.servicedInfo.get('refundedMoney') and VirtualCoin(
  2794. # self.servicedInfo.get('refundedMoney')) > VirtualCoin('0.00'):
  2795. # rv['isRefund'] = True
  2796. # rv['refundedMoney'] = str(self.servicedInfo.get('refundedMoney'))
  2797. # elif self.servicedInfo.get('refundedCash') and RMB(self.servicedInfo.get('refundedCash')) > RMB('0.00'):
  2798. # rv['isRefund'] = True
  2799. # rv['refundedCash'] = str(self.servicedInfo.get('refundedCash'))
  2800. # else:
  2801. # if self.status == ConsumeRecord.Status.REFUND: # 蓝牙会设置退费状态
  2802. # if VirtualCoin(self.coin) > VirtualCoin('0.00'):
  2803. # rv['isRefund'] = True
  2804. # rv['refundedMoney'] = str(self.coin)
  2805. # elif self.recharge_record_id:
  2806. # refund_order = RefundMoneyRecord.objects(rechargeObjId = str(self.recharge_record_id)).first()
  2807. # if refund_order:
  2808. # rv['isRefund'] = True
  2809. # rv['refundedCash'] = str(refund_order.money)
  2810. # else:
  2811. # if VirtualCoin(self.coin) > VirtualCoin('0.00'): # 失败的情况下根本不会扣费
  2812. # rv['isRefund'] = True
  2813. # rv['refundedMoney'] = str(self.coin)
  2814. #
  2815. # return rv
  2816. #
  2817. # @property
  2818. # def info_for_feedback(self):
  2819. # rv = {
  2820. # 'orderId': str(self.id),
  2821. # 'orderNo': self.orderNo,
  2822. # 'coins': self.coin,
  2823. # 'deviceStartTime': self.device_start_time,
  2824. # 'deviceFinishedTime': self.device_finished_time
  2825. # }
  2826. #
  2827. # rv.update(self.device_identity_info)
  2828. #
  2829. # rv.update(self.refund_info)
  2830. #
  2831. # if self.used_port != -1:
  2832. # rv.update({
  2833. # 'port': self.used_port
  2834. # })
  2835. #
  2836. # if self.recharge_record_id:
  2837. # from apps.web.common.proxy import ClientRechargeModelProxy
  2838. # recharge_record = ClientRechargeModelProxy.get_one(
  2839. # shard_filter = {'ownerId': self.ownerId}, id = self.recharge_record_id) # type: RechargeRecord
  2840. #
  2841. # if recharge_record:
  2842. # rv.update({
  2843. # 'rechargeTradeNo': recharge_record.orderNo,
  2844. # 'money': self.money
  2845. # })
  2846. #
  2847. # return rv
  2848. #
  2849. class CardRechargeOrder(Searchable):
  2850. """
  2851. 卡充值订单
  2852. """
  2853. orderNo = StringField(verbose_name=u"订单号", default="")
  2854. cardId = StringField(verbose_name=u"实体卡ID", default="")
  2855. cardNo = StringField(verbose_name=u"实体卡NO", default="")
  2856. # 卡绑定的发行地址
  2857. groupId = StringField(verbose_name=u"设备地址编号", default="")
  2858. address = StringField(verbose_name=u"设备地址", default="")
  2859. groupName = StringField(verbose_name=u"交易场地", default="")
  2860. openId = StringField(verbose_name=u"openId", default="")
  2861. money = MonetaryField(verbose_name=u"付款金额", default=RMB('0.00'))
  2862. coins = MonetaryField(verbose_name=u"充值金额", default=RMB('0.00'))
  2863. remarks = StringField(verbose_name=u"备注", default="")
  2864. dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'生成时间')
  2865. sequanceNo = StringField(verbose_name=u"交易流水号", default="")
  2866. status = StringField(verbose_name=u"状态", default="")
  2867. rechargeType = StringField(verbose_name=u"充值类型,一般分网络充值、线下设备充值", default="") # netpay/device
  2868. dealerId = StringField(verbose_name=u'订单ownerId, 集群需要', default=None)
  2869. rechargeNo = ObjectIdField(verbose_name=u"订单ID")
  2870. operationLog = ListField(verbose_name=u'历史处理结果', default=[])
  2871. processingLog = DictField(verbose_name=u'')
  2872. meta = {
  2873. "collection": "cardRechargeOrder",
  2874. "db_alias": "default",
  2875. }
  2876. def __repr__(self):
  2877. return '<CardRechargeOrder(id=%s, orderNo=%s, cardNo=%s)>' % (str(self.id), self.orderNo, self.cardNo)
  2878. @cached_property
  2879. def rechargeOrder(self):
  2880. return RechargeRecord.objects.filter(id=self.rechargeNo).first()
  2881. @property
  2882. def chargeAmount(self):
  2883. return self.money
  2884. @property
  2885. def bestowAmount(self):
  2886. return RMB(self.coins) - RMB(self.money)
  2887. # 获取卡的订单,但是没有支付的
  2888. @staticmethod
  2889. def get_last_to_do_one(cardId):
  2890. obj = CardRechargeOrder.objects(
  2891. cardId=cardId, status='finishedPay',
  2892. rechargeType__in=["netpay", "sendCoin"]
  2893. ).order_by('-dateTimeAdded').first()
  2894. return obj
  2895. # 获取卡的订单,但是没有支付的
  2896. @staticmethod
  2897. def get_to_do_list(cardId):
  2898. objs = CardRechargeOrder.objects.filter(
  2899. cardId=cardId, status='finishedPay',
  2900. rechargeType__in=['netpay', "sendCoin"]
  2901. )
  2902. money, coins = RMB(0), RMB(0)
  2903. orderNos = []
  2904. cardOrderNos = []
  2905. for obj in objs:
  2906. money += obj.money
  2907. coins += obj.coins
  2908. orderNos.append(obj.rechargeNo)
  2909. cardOrderNos.append(obj.orderNo)
  2910. return money, coins, orderNos, cardOrderNos
  2911. # 更新卡的为到账订单,更新为已经支付
  2912. @staticmethod
  2913. def update_card_order_has_finished(cardId):
  2914. CardRechargeOrder.get_collection().update(
  2915. {'cardId': cardId, 'status': 'finishedPay', 'rechargeType': {"$in": ["netpay", "sendCoin"]}},
  2916. {'$set': {'status': 'finished'}},
  2917. multi = True
  2918. )
  2919. def init_processing_log(self, device, sendMoney, preBalance):
  2920. # type: (DeviceDict, RMB, RMB)->CardRechargeOrder
  2921. self.processingLog = {
  2922. 'devNo': device.devNo,
  2923. 'logicalCode': device.logicalCode,
  2924. 'sendMoney': sendMoney.mongo_amount,
  2925. 'preBalance': preBalance.mongo_amount,
  2926. 'code': device.get('devType', {}).get('code', ''),
  2927. 'time': datetime.datetime.now()
  2928. }
  2929. # return self.save()
  2930. def update_after_recharge_ic_card(self, device, sendMoney, preBalance,
  2931. result = ErrorCode.SUCCESS, description = '', syncBalance = None):
  2932. # type: (DeviceDict, RMB, RMB, int, basestring, Optional[RMB])->CardRechargeOrder
  2933. log = {
  2934. 'devNo': device.devNo,
  2935. 'logicalCode': device.logicalCode,
  2936. 'sendMoney': sendMoney.mongo_amount,
  2937. 'preBalance': preBalance.mongo_amount,
  2938. 'code': device.get('devType', {}).get('code', ''),
  2939. 'result': result,
  2940. 'description': description,
  2941. 'time': datetime.datetime.now()
  2942. }
  2943. if syncBalance:
  2944. log['syncBalance'] = syncBalance.mongo_amount
  2945. self.operationLog.append(log)
  2946. if result == ErrorCode.SUCCESS:
  2947. self.status = 'finished'
  2948. return self.save()
  2949. def update_after_recharge_id_card(self, device, balance, preBalance):
  2950. # type: (Optional[DeviceDict], RMB, RMB)->CardRechargeOrder
  2951. if device:
  2952. log = {
  2953. 'devNo': device.devNo,
  2954. 'logicalCode': device.logicalCode,
  2955. 'balance': balance.mongo_amount,
  2956. 'preBalance': preBalance.mongo_amount,
  2957. 'code': device.get('devType', {}).get('code', ''),
  2958. 'time': datetime.datetime.now()
  2959. }
  2960. else:
  2961. log = {
  2962. 'balance': balance.mongo_amount,
  2963. 'preBalance': preBalance.mongo_amount,
  2964. 'time': datetime.datetime.now()
  2965. }
  2966. self.operationLog.append(log)
  2967. self.status = 'finished'
  2968. return self.save()
  2969. @staticmethod
  2970. def new_one(openId, cardId, cardNo, money, coins, group, rechargeId, rechargeType = 'netpay'):
  2971. # type:(str, str, str, RMB, RMB, GroupDict, ObjectId, str)->CardRechargeOrder
  2972. newRcd = CardRechargeOrder(
  2973. orderNo = OrderNoMaker.make_order_no_32(identifier = cardNo,
  2974. main_type = OrderMainType.PAY,
  2975. sub_type = UserPaySubType.CARD_RECHARGE_RECORD),
  2976. cardId = str(cardId),
  2977. cardNo = cardNo,
  2978. openId = openId,
  2979. money = money,
  2980. coins = RMB(coins.amount),
  2981. status = 'finishedPay',
  2982. rechargeType = rechargeType,
  2983. dealerId = group.ownerId,
  2984. groupId = group.groupId,
  2985. address = group.address,
  2986. groupName = group.groupName,
  2987. rechargeNo = rechargeId
  2988. )
  2989. logger.info('make card recharge order = %s' % repr(newRcd))
  2990. try:
  2991. return newRcd.save()
  2992. except Exception as e:
  2993. logger.exception('save cardId = %s; cardNo = %s; recharge error = %s' % (cardId, cardNo, e))
  2994. return None
  2995. def to_detail(self):
  2996. # type: ()->dict
  2997. """
  2998. :return:
  2999. """
  3000. return {
  3001. 'orderNo': self.orderNo,
  3002. 'cardNo': self.cardNo,
  3003. 'openId': self.openId,
  3004. 'groupId': self.groupId,
  3005. 'amount': self.money,
  3006. 'devNo': '',
  3007. 'logicalCode': '',
  3008. 'devType': '',
  3009. 'groupNumber': '',
  3010. 'groupName': self.groupName,
  3011. 'address': self.address
  3012. }
  3013. @classmethod
  3014. def get_by_rechargeRecord(cls, record):
  3015. # type:(RechargeRecord)->CardRechargeOrder
  3016. return cls.objects(rechargeNo = record.id).first()
  3017. class CardRechargeRecord(Searchable):
  3018. orderNo = StringField(verbose_name = "关联订单号", default = "") # 关联订单处理卡退费
  3019. cardId = StringField(verbose_name = "实体卡ID", default = "")
  3020. cardNo = StringField(verbose_name = "实体卡号", default = "")
  3021. openId = StringField(verbose_name = "openId", default = "")
  3022. ownerId = ObjectIdField(verbose_name = "dealerId")
  3023. money = MonetaryField(verbose_name = "付款金额", default = RMB('0.00'))
  3024. coins = MonetaryField(verbose_name = "充值金额", default = VirtualCoin('0.00'))
  3025. chargeBalance = MonetaryField(verbose_name="充值余额", default=RMB(0))
  3026. bestowBalance = MonetaryField(verbose_name="赠送余额", default=RMB(0))
  3027. preChargeBalance = MonetaryField(verbose_name="之前充值余额", default=RMB(0))
  3028. preBestowBalance = MonetaryField(verbose_name="之前赠送余额", default=RMB(0))
  3029. devNo = StringField(verbose_name = "设备ID", default = "")
  3030. devTypeCode = StringField(verbose_name = "设备类型编码", default = "")
  3031. logicalCode = StringField(verbose_name = "设备逻辑编码", default = "")
  3032. groupId = StringField(verbose_name = "设备地址编号", default="")
  3033. address = StringField(verbose_name = "设备地址", default = "")
  3034. groupNumber = StringField(verbose_name = "设备", default = "")
  3035. groupName = StringField(verbose_name = "交易场地", default = "")
  3036. #: 在部分场合,设备会失败启动,但这时应该添加该字段为False方便追溯
  3037. remarks = StringField(verbose_name="备注", default="")
  3038. dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name='生成时间')
  3039. sequanceNo = StringField(verbose_name="交易流水号,有些刷卡的会上报", default="")
  3040. status = StringField(verbose_name="状态", default="")
  3041. rechargeType = StringField(verbose_name="充值类型,一般分网络充值、线下设备充值", default="")
  3042. meta = {
  3043. "collection": "cardRechargeRecord",
  3044. "db_alias": "default",
  3045. # "shard_key":('cardId',),
  3046. }
  3047. @property
  3048. def amount(self):
  3049. return self.money
  3050. @property
  3051. def balance(self):
  3052. return self.chargeBalance + self.bestowBalance
  3053. @property
  3054. def preBalance(self):
  3055. return self.preChargeBalance + self.preBestowBalance
  3056. @property
  3057. def dealer(self):
  3058. return Dealer.objects(id=self.ownerId).get()
  3059. @staticmethod
  3060. def make_no():
  3061. timestamp = generate_timestamp_ex()
  3062. random_int = random.randint(1, 1000)
  3063. return "%d%03d" % (timestamp, random_int)
  3064. @classmethod
  3065. def add_self_card_record(cls, cardId, group, money, device):
  3066. newRcd = CardRechargeRecord(
  3067. cardId = cardId,
  3068. ownerId = ObjectId(group.ownerId),
  3069. money = money,
  3070. coins = money,
  3071. groupId = group.groupId,
  3072. address = group.address,
  3073. groupName = group.groupName,
  3074. status = 'success',
  3075. rechargeType = 'dealer_oper',
  3076. devNo = device.devNo,
  3077. devTypeCode = device.get('devType', {}).get('code', ''),
  3078. logicalCode = device.logicalCode,
  3079. groupNumber = device.groupNumber
  3080. )
  3081. try:
  3082. return newRcd.save()
  3083. except Exception as e:
  3084. logger.exception('save card consume rcd error=%s' % e)
  3085. @staticmethod
  3086. def add_record(card, group, order, device=None):
  3087. # type:(Card, GroupDict, CardRechargeOrder, Optional[DeviceDict])->CardRechargeRecord
  3088. newRcd = CardRechargeRecord(
  3089. cardId=str(card.id),
  3090. cardNo=card.cardNo,
  3091. openId=card.openId,
  3092. ownerId=ObjectId(group.ownerId),
  3093. money=order.money,
  3094. coins=order.coins,
  3095. chargeBalance=order.chargeAmount + card.chargeBalance,
  3096. bestowBalance=order.bestowAmount + card.bestowBalance,
  3097. preChargeBalance=card.chargeBalance,
  3098. preBestowBalance=card.bestowBalance,
  3099. groupId=group.groupId,
  3100. address=group.address,
  3101. groupName=group.groupName,
  3102. status='success',
  3103. rechargeType=order.rechargeType
  3104. )
  3105. if device:
  3106. newRcd.devNo = device.devNo
  3107. newRcd.logicalCode = device.logicalCode
  3108. newRcd.devTypeCode = device.devTypeCode
  3109. newRcd.groupNumber = device.groupNumber
  3110. try:
  3111. return newRcd.save()
  3112. except Exception as e:
  3113. logger.exception('save card consume rcd error=%s' % e)
  3114. class CardConsumeRecord(Searchable):
  3115. orderNo = StringField(verbose_name = "订单号", default = "")
  3116. openId = StringField(verbose_name = "微信ID", default = "")
  3117. cardId = StringField(verbose_name = "实体卡ID", default = "")
  3118. money = MonetaryField(verbose_name = "消耗的钱(硬币)", default = RMB('0.00'))
  3119. balance = MonetaryField(verbose_name = "当前余额(硬币)", default = RMB('0.00'))
  3120. devNo = StringField(verbose_name = "设备ID", default = "")
  3121. devType = StringField(verbose_name = "设备类型", default = "")
  3122. logicalCode = StringField(verbose_name = "设备逻辑编码", default = "")
  3123. groupId = StringField(verbose_name = "设备地址编号", default = "")
  3124. address = StringField(verbose_name = "设备地址", default = "")
  3125. groupNumber = StringField(verbose_name = "设备", default = "")
  3126. groupName = StringField(verbose_name = "交易场地", default = "")
  3127. result = StringField(verbose_name = "消费结果", default = "")
  3128. remarks = StringField(verbose_name = "备注", default = "")
  3129. desc = StringField(verbose_name = "描述", default = "")
  3130. dateTimeAdded = DateTimeField(verbose_name = '生成时间', default = datetime.datetime.now)
  3131. finishedTime = DateTimeField(verbose_name = '结束时间', default = datetime.datetime.now)
  3132. servicedInfo = DictField(verbose_name = '服务内容', default = {})
  3133. sid = StringField(verbose_name = "从设备上获取的流水号", default = "")
  3134. linkedConsumeRcdOrderNo = StringField(verbose_name = u'关联的消费订单号.发布后去掉, 两者一致', default = '')
  3135. meta = {
  3136. "collection": "CardConsumeRecord",
  3137. "db_alias": "default",
  3138. # "shard_key":('orderNo',),
  3139. }
  3140. @staticmethod
  3141. def make_no():
  3142. timestamp = generate_timestamp_ex()
  3143. random_int = random.randint(1, 1000)
  3144. return "%d%03d" % (timestamp, random_int)
  3145. class ServiceProgress(Searchable):
  3146. open_id = StringField(verbose_name = 'open id', default = '')
  3147. device_imei = StringField(verbose_name = 'device imei', default = '')
  3148. port = IntField(verbose_name = 'port', default = 0)
  3149. isFinished = BooleanField(verbose_name = 'isFinished', default = False)
  3150. devTypeCode = StringField(verbose_name = 'devTypeCode', default = '')
  3151. cardId = StringField(verbose_name = 'cardNo', default = '')
  3152. attachParas = DictField(verbose_name = 'attachParas', default = {})
  3153. consumeOrder = DictField(verbose_name = 'consumeOrder', default = {})
  3154. finished_time = IntField(verbose_name = 'finished time', default = 0)
  3155. start_time = IntField(verbose_name = 'start_time', default = 0)
  3156. consumes = ListField(verbose_name = "对应的所有消费记录单号", default = [])
  3157. # 应该用一个状态表示服务的执行情况,包括等待服务、服务正在进行、服务结束。只有我们才需要此字段
  3158. # 但是只有我们自己的主板支持每个订单的执行情况反馈以及查询,所以没有办法,以前都是裹在一起,现在分开逻辑更清晰。
  3159. status = StringField(verbose_name = u'执行状态', default = 'running') # waiting/working/finished/failed
  3160. weifuleOrderNo = StringField(verbose_name = u'微付乐订单编号,设备也会产生订单', default = '')
  3161. runningTime = DateTimeField(verbose_name = u'开始运行的时间')
  3162. datetimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
  3163. expireAt = DateTimeField(verbose_name = u"TTL到期时间", default = None)
  3164. meta = {
  3165. 'collection': 'service_progress',
  3166. 'db_alias': 'logdata'
  3167. }
  3168. @property
  3169. def used_port(self):
  3170. """
  3171. 兼容性属性. 以前是整形, 后面全部为字符串型
  3172. :return:
  3173. """
  3174. return str(self.port)
  3175. @property
  3176. def order_no(self):
  3177. return self.attachParas.get('orderNo', None)
  3178. # 更新progress,以及关联的消费日志记录。这个函数一般是服务结束的时候调用,以刷新progress,有时候,结束时
  3179. # 才扣费的场景,扣费的时候会直接把使用后的消耗,记录到消费日志中,就不需要刷新消费日志
  3180. @staticmethod
  3181. def update_progress_and_consume_rcd(ownerId, queryDict, consumeDict, updateConsume = True, progressDict = None):
  3182. progressDict = {
  3183. 'isFinished': True, 'finished_time': int(time.time())
  3184. } if progressDict is None else progressDict
  3185. if 'isFinished' in progressDict and progressDict['isFinished']:
  3186. progressDict.update({'expireAt': datetime.datetime.now()})
  3187. rcds = ServiceProgress.get_collection().find(queryDict).sort('start_time')
  3188. if rcds.count() == 0:
  3189. logger.error('can not find the progress info')
  3190. return
  3191. orderIdList, orderNoList, cardOrderNoList = [], [], []
  3192. for rcd in rcds: # type: Dict
  3193. consumeOrder = rcd.get('consumeOrder', {})
  3194. if 'consumeRecordId' in consumeOrder and consumeOrder['consumeRecordId']:
  3195. orderIdList.append(consumeOrder['consumeRecordId'])
  3196. elif 'orderNo' in consumeOrder and consumeOrder['orderNo']:
  3197. orderNoList.append(consumeOrder['orderNo'])
  3198. if 'cardOrderNo' in consumeOrder:
  3199. cardOrderNoList.append(consumeOrder['cardOrderNo'])
  3200. # 更新进程的数据
  3201. try:
  3202. ServiceProgress.get_collection().update(queryDict, {'$set': progressDict}, multi = True)
  3203. except Exception, e:
  3204. logger.exception('update service progress e=%s' % e)
  3205. if not updateConsume: # 不需要更新就直接返回
  3206. return
  3207. # 更新订单中的时间以及服务信息。如果是卡的信息,因为找不到order,会直接pass
  3208. # 如果是多条消费单,但是最终只有一条结束事件的场景,统计以及消费信息只记录到第一单上
  3209. isFinished = progressDict and progressDict.get('isFinished', False)
  3210. consume_orders = []
  3211. for orderNo in orderNoList:
  3212. rcd = ConsumeRecord.objects(ownerId = ownerId, orderNo = orderNo).first() # type: ConsumeRecord
  3213. consume_orders.append(rcd)
  3214. for orderId in orderIdList:
  3215. rcd = ConsumeRecord.objects(ownerId = ownerId, id = orderId).first() # type: ConsumeRecord
  3216. consume_orders.append(rcd)
  3217. count = 0
  3218. uart_data = consumeDict.pop('uartData', None)
  3219. for rcd in consume_orders: # type: ConsumeRecord
  3220. try:
  3221. count += 1
  3222. pre_aggInfo = rcd.aggInfo
  3223. # 如果是结束更新,相应的信息只需要更新到第一条中。非结束的,全部都刷新。
  3224. if not isFinished:
  3225. updated = rcd.update(
  3226. finishedTime = datetime.datetime.now(),
  3227. servicedInfo = consumeDict,
  3228. status = ConsumeRecord.Status.FINISHED)
  3229. elif count == 1:
  3230. if uart_data:
  3231. updated = rcd.update(
  3232. finishedTime = datetime.datetime.now(),
  3233. servicedInfo = consumeDict,
  3234. status = ConsumeRecord.Status.FINISHED,
  3235. attachParas__uartData = uart_data)
  3236. else:
  3237. updated = rcd.update(
  3238. finishedTime = datetime.datetime.now(),
  3239. servicedInfo = consumeDict,
  3240. status = ConsumeRecord.Status.FINISHED)
  3241. else:
  3242. continue
  3243. if not updated:
  3244. logger.error('%r updated failed' % (rcd,))
  3245. if isFinished and count == 1: # 只需要汇总一次使用的数据即可
  3246. try:
  3247. valueDict = {}
  3248. for kind, value in consumeDict.items():
  3249. if kind in DEALER_CONSUMPTION_AGG_KIND.choices():
  3250. if kind in pre_aggInfo:
  3251. #: 会在发起service阶段更新一次报表
  3252. #: 此处为结束时更新, 应该更新新数据 - 初始更新的数据
  3253. valueDict[kind] = float(str(value)) - float(str(pre_aggInfo[kind]))
  3254. else:
  3255. valueDict[kind] = value
  3256. status = rcd.update_agg_info(valueDict)
  3257. if status:
  3258. record_consumption_stats(rcd)
  3259. else:
  3260. logger.error(
  3261. '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (rcd,))
  3262. except Exception, e:
  3263. logger.exception('update agg info error=%s' % e)
  3264. except Exception as e:
  3265. logger.exception('update consume rcd e=%s' % e)
  3266. continue
  3267. for orderNo in cardOrderNoList:
  3268. if orderNo == '':
  3269. continue
  3270. try:
  3271. CardConsumeRecord.get_collection().update_one({'orderNo': orderNo}, {
  3272. '$set': {'finished_time': int(time.time()), 'servicedInfo': consumeDict}})
  3273. except Exception, e:
  3274. logger.exception('update consume rcd e=%s' % e)
  3275. continue
  3276. @staticmethod
  3277. def register_card_service(dev, port, card, consumeOrder = None, finishedTime = None):
  3278. # type:(DeviceDict, int, Card, dict, int)->None
  3279. consumeOrder = {} if consumeOrder is None else consumeOrder
  3280. finishedTime = int(time.time()) + 24 * 60 * 60 if finishedTime is None else finishedTime
  3281. ServiceProgress.get_collection().insert({
  3282. 'device_imei': dev.devNo,
  3283. 'devTypeCode': dev.devTypeCode,
  3284. 'cardId': str(card.id),
  3285. 'port': port,
  3286. 'finished_time': finishedTime,
  3287. 'start_time': int(time.time()),
  3288. 'open_id': card.openId,
  3289. 'consumeOrder': consumeOrder,
  3290. 'isFinished': False,
  3291. 'attachParas': {},
  3292. 'expireAt': datetime.datetime.now() + datetime.timedelta(days = 91)
  3293. })
  3294. @staticmethod
  3295. def register_card_service_for_weifule(dev, port, card, consumeOrder):
  3296. ServiceProgress.get_collection().insert({
  3297. 'device_imei': dev['devNo'],
  3298. 'devTypeCode': dev['devType']['code'],
  3299. 'cardId': str(card.id),
  3300. 'port': port,
  3301. 'finished_time': int(time.time()) + 24 * 60 * 60,
  3302. 'start_time': int(time.time()),
  3303. 'open_id': card.openId,
  3304. 'consumeOrder': consumeOrder,
  3305. 'isFinished': False,
  3306. 'attachParas': {},
  3307. 'status': 'waiting',
  3308. 'weifuleOrderNo': consumeOrder['orderNo'],
  3309. 'expireAt': datetime.datetime.now() + datetime.timedelta(days = 91)
  3310. })
  3311. @classmethod
  3312. def new_progress_for_order(cls, order, device, cache_info):
  3313. # type:(ConsumeRecord, DeviceDict, dict)->None
  3314. try:
  3315. consumeOrder = {
  3316. 'consumeRecordId': str(order.id),
  3317. 'orderNo': order.orderNo,
  3318. 'coin': VirtualCoin(cache_info['coins']).mongo_amount,
  3319. 'consumeType': cache_info.get('consumeType') or (
  3320. 'mobile_vcard' if u'虚拟卡' in order.remarks else 'mobile')
  3321. }
  3322. if 'money' in cache_info:
  3323. consumeOrder.update({'money': RMB(cache_info['money']).mongo_amount})
  3324. if 'unit' in cache_info:
  3325. consumeOrder.update({'unit': cache_info['unit']})
  3326. if 'subOrderNos' in cache_info:
  3327. consumeOrder.update({'subOrderNos': cache_info['subOrderNos']})
  3328. if 'needKind' in cache_info and 'needValue' in cache_info:
  3329. consumeOrder.update({
  3330. cache_info['needKind']: cache_info['needValue']
  3331. })
  3332. if 'cardId' in cache_info:
  3333. consumeOrder.update({'cardId': cache_info['cardId']})
  3334. update_dict = {
  3335. 'open_id': cache_info['openId'],
  3336. 'device_imei': device.devNo,
  3337. 'devTypeCode': device['devType']['code'],
  3338. 'port': order.used_port,
  3339. 'attachParas': order.attachParas,
  3340. 'start_time': int(
  3341. Arrow.fromdatetime(order.startTime, tzinfo = settings.TIME_ZONE).timestamp),
  3342. 'finished_time': int(cache_info['estimatedTs']),
  3343. 'consumeOrder': consumeOrder,
  3344. 'isFinished': False,
  3345. 'expireAt': datetime.datetime.now() + datetime.timedelta(days = 91)
  3346. }
  3347. cls.objects(open_id = order.openId,
  3348. device_imei = device.devNo,
  3349. port = order.used_port).update_one(upsert = True, **update_dict)
  3350. except Exception as e:
  3351. logger.exception(e)
  3352. @classmethod
  3353. def finish_progress_for_order(cls, order, device):
  3354. # type:(ConsumeRecord, DeviceDict)->None
  3355. cls.get_collection().update_one(filter = {
  3356. 'open_id': order.openId,
  3357. 'device_imei': device.devNo,
  3358. 'port': int(order.used_port),
  3359. 'consumeOrder.consumeRecordId': str(order.id)
  3360. }, update = {
  3361. '$set': {
  3362. 'isFinished': True,
  3363. 'finished_time': Arrow.fromdatetime(order.finishedTime,
  3364. tzinfo = settings.TIME_ZONE).timestamp,
  3365. 'expireAt': datetime.datetime.now()
  3366. }
  3367. }, upsert = False)
  3368. class Card(Searchable):
  3369. cardNo = StringField(verbose_name="卡号", default="")
  3370. cardType = StringField(verbose_name="卡类型", default="")
  3371. openId = StringField(verbose_name="绑定的微信", default="")
  3372. nickName = StringField(verbose_name="用户昵称", default="")
  3373. productAgentId = StringField(verbose_name="当前用户绑定的平台代理商", default = "")
  3374. cardName = StringField(verbose_name="持卡人姓名", default="")
  3375. phone = StringField(verbose_name="手机号码", default="")
  3376. # 绑定的管理APP信息
  3377. managerialAppId = StringField(verbose_name="管理公众号AppId", default="")
  3378. managerialOpenId = StringField(verbose_name="管理openId", default="")
  3379. dealerId = StringField(verbose_name="经销商ID", default="")
  3380. groupId = StringField(verbose_name="设备组ID", default="")
  3381. agentId = StringField(verbose_name="代理商ID", default="")
  3382. # 以下信息为卡固有信息
  3383. remarks = StringField(verbose_name="备注", default="")
  3384. chargeBalance = MonetaryField(verbose_name="充值余额", default=RMB(0))
  3385. bestowBalance = MonetaryField(verbose_name=u"赠送余额", default=RMB(0))
  3386. isHaveBalance = BooleanField(verbose_name="是否能够获取到卡的余额数据", default=True)
  3387. lastMaxBalance = MonetaryField(verbose_name="最后一次充卡金额", default=RMB('0.00'))
  3388. boundVirtualCardId = ObjectIdField(verbose_name=u"绑定的虚拟卡券ID")
  3389. showBalance = MonetaryField(verbose_name=u"显示到设备上的卡余额,只有微付乐才会用此字段", default=RMB('0.00'))
  3390. # 最近一次刷卡的设备
  3391. devNo = StringField(verbose_name="设备IMEI", default="")
  3392. devTypeCode = StringField(verbose_name="设备类型代码", default="")
  3393. frozen = BooleanField(verbose_name="卡挂失状态", default=False)
  3394. status = StringField(verbose_name="卡状态", default='active')
  3395. dateTimeAdded = DateTimeField(verbose_name=u"添加卡的时间", default=datetime.datetime.now)
  3396. attachParas = DictField(verbose_name='附加参数', default={})
  3397. ongoingList = ListField(field=DictField(), verbose=u'冻结的金额')
  3398. search_fields = ('cardNo', 'cardName', 'phone')
  3399. meta = {
  3400. "collection": "recharge_card",
  3401. "db_alias": "default",
  3402. }
  3403. def __repr__(self):
  3404. return '<Card(id={}, cardNo={})>'.format(str(self.id), self.cardNo)
  3405. @cached_property
  3406. def group(self):
  3407. if not self.groupId:
  3408. return None
  3409. return Group.get_group(self.groupId)
  3410. @cached_property
  3411. def user(self):
  3412. if not self.isBinded:
  3413. return None
  3414. return MyUser.objects.filter(openId=self.openId, groupId=self.groupId).first()
  3415. @property
  3416. def balance(self):
  3417. return self.chargeBalance + self.bestowBalance
  3418. @property
  3419. def nickname(self):
  3420. return self.nickName
  3421. @property
  3422. def is_id_card(self):
  3423. # type: ()->bool
  3424. return self.cardType == RECHARGE_CARD_TYPE.ID
  3425. @property
  3426. def is_ic_card(self):
  3427. # type: ()->bool
  3428. return self.cardType == RECHARGE_CARD_TYPE.IC
  3429. @property
  3430. def isBinded(self):
  3431. """
  3432. 用户的绑定状态
  3433. """
  3434. return True if self.openId and self.openId != Const.DEFAULT_CARD_OPENID else False
  3435. def recharge(self, money, bestowMoney):
  3436. assert isinstance(money, RMB), 'coins had to be VirtualCoin'
  3437. assert isinstance(bestowMoney, RMB), 'money has to be RMB'
  3438. updated = self.update(
  3439. inc__chargeBalance=money,
  3440. inc__bestowBalance=bestowMoney,
  3441. )
  3442. if not updated:
  3443. raise PostPayOrderError(u'余额和累计充值更新失败')
  3444. return self.reload()
  3445. def account_consume(self, order): # type:(ConsumeRecord) -> None
  3446. self.reload()
  3447. payment = order.payment
  3448. # 获取支付的钱
  3449. consumeAmount = payment.actualAmount
  3450. consumeBestowAmount = payment.totalAmount - consumeAmount
  3451. # 获取当前的余额
  3452. CardBalanceLog.consume(
  3453. self,
  3454. afterAmount=self.chargeBalance,
  3455. afterBestowAmount=self.bestowBalance,
  3456. consumeAmount=consumeAmount,
  3457. consumeBestowAmount=consumeBestowAmount,
  3458. order=order
  3459. )
  3460. def account_refund(self, order): # type:(ConsumeRecord) -> None
  3461. self.reload()
  3462. refund = order.refund
  3463. # 获取支付的钱
  3464. refundAmount = refund.actualAmount
  3465. refundBestowAmount = refund.totalAmount - refundAmount
  3466. CardBalanceLog.refund(
  3467. self,
  3468. afterAmount=self.chargeBalance,
  3469. afterBestowAmount=self.bestowBalance,
  3470. refundAmount=refundAmount,
  3471. refundBestowAmount=refundBestowAmount,
  3472. order=order
  3473. )
  3474. def account_recharge(self, order): # type:(RechargeRecord) -> None
  3475. # 获取支付的钱
  3476. chargeAmount = order.chargeAmount
  3477. bestowAmount = order.bestowAmount
  3478. CardBalanceLog.recharge(
  3479. self,
  3480. afterAmount=self.chargeBalance,
  3481. afterBestowAmount=self.bestowBalance,
  3482. chargeAmount=chargeAmount,
  3483. chargeBestowAmount=bestowAmount,
  3484. order=order
  3485. )
  3486. @classmethod
  3487. def freeze_balance(cls, transaction_id, payment):
  3488. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  3489. chargeBalanceField = cls.chargeBalance.name
  3490. bestowBalanceField = cls.bestowBalance.name
  3491. for deduct in payment.deduct_list:
  3492. query = {
  3493. '_id': ObjectId(deduct['id']),
  3494. 'ongoingList.transaction_id': {
  3495. '$ne': transaction_id
  3496. }
  3497. }
  3498. update = {
  3499. '$inc': {
  3500. chargeBalanceField: (-RMB(deduct[chargeBalanceField])).mongo_amount,
  3501. bestowBalanceField: (-RMB(deduct[bestowBalanceField])).mongo_amount
  3502. },
  3503. '$addToSet': {
  3504. 'ongoingList': {
  3505. 'transaction_id': transaction_id,
  3506. chargeBalanceField: deduct[chargeBalanceField],
  3507. bestowBalanceField: deduct[bestowBalanceField]
  3508. }
  3509. }
  3510. }
  3511. bulker.update(query, update)
  3512. result = bulker.execute()
  3513. logger.debug(result['info'])
  3514. if result['success'] == 0:
  3515. raise ServiceException({'result': 0, 'description': u"扣款失败(1001)"})
  3516. else:
  3517. if len(result['info']['writeErrors']) != 0:
  3518. raise ServiceException({'result': 0, 'description': u"扣款失败(1002)"})
  3519. else:
  3520. return True
  3521. @classmethod
  3522. def clear_frozen_balance(cls, transaction_id, payment, refund):
  3523. try:
  3524. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  3525. chargeBalanceField = cls.chargeBalance.name
  3526. bestowBalanceField = cls.bestowBalance.name
  3527. for deduct in refund.deduct_list:
  3528. query = {
  3529. '_id': ObjectId(deduct['id']),
  3530. 'ongoingList': {
  3531. '$elemMatch': {
  3532. 'transaction_id': transaction_id
  3533. }
  3534. }
  3535. }
  3536. update = {
  3537. '$inc': {
  3538. chargeBalanceField: deduct[chargeBalanceField],
  3539. bestowBalanceField: deduct[bestowBalanceField],
  3540. },
  3541. '$pull': {
  3542. 'ongoingList': {
  3543. 'transaction_id': transaction_id
  3544. }
  3545. }
  3546. }
  3547. bulker.update(query, update)
  3548. result = bulker.execute()
  3549. logger.debug(result['info'])
  3550. if result['success'] == 0:
  3551. return False
  3552. else:
  3553. if len(result['info']['writeErrors']) != 0:
  3554. return False
  3555. else:
  3556. return True
  3557. except Exception as e:
  3558. logger.exception(e)
  3559. return False
  3560. @classmethod
  3561. def recover_frozen_balance(cls, transaction_id, deduct_list):
  3562. bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
  3563. for deduct in deduct_list:
  3564. query = {
  3565. '_id': ObjectId(deduct['id']),
  3566. 'ongoingList': {
  3567. '$elemMatch': {'transaction_id': transaction_id}}}
  3568. update = {
  3569. '$inc': {
  3570. 'balance': deduct['coins']
  3571. },
  3572. '$pull': {
  3573. 'ongoingList': {
  3574. 'transaction_id': transaction_id, 'frozenCoins': deduct['coins']
  3575. }}
  3576. }
  3577. bulker.update(query, update)
  3578. result = bulker.execute()
  3579. if result['success'] == 0:
  3580. logger.error(result['info'])
  3581. return False
  3582. else:
  3583. if len(result['info']['writeErrors']) != 0:
  3584. logger.error(result['info'])
  3585. return False
  3586. else:
  3587. return True
  3588. @classmethod
  3589. def fake_one(cls, groupId, cardId='fake_id', cardNo='fake'):
  3590. return cls(id=cardId, cardNo=cardNo, openId='', productAgentId='', groupId=groupId)
  3591. @staticmethod
  3592. def get_card_status(cardId):
  3593. status = serviceCache.get(cardId, 'idle')
  3594. return status
  3595. @staticmethod
  3596. def set_card_status(cardId, status):
  3597. serviceCache.set(cardId, status, 10)
  3598. @staticmethod
  3599. def get_dev_cur_card(devNo):
  3600. return serviceCache.get('%s_cardId' % devNo, None)
  3601. @staticmethod
  3602. def set_dev_cur_card(devNo, cardInfo):
  3603. serviceCache.set('%s_cardId' % devNo, cardInfo, 90)
  3604. @property
  3605. def bound_virtual_card(self):
  3606. # type: ()->Optional[UserVirtualCard]
  3607. if self.boundVirtualCardId:
  3608. now = datetime.datetime.now()
  3609. virtual_card = UserVirtualCard.objects(id = self.boundVirtualCardId,
  3610. expiredTime__gte = now,
  3611. startTime__lte = now).first()
  3612. return virtual_card
  3613. else:
  3614. return None
  3615. def bind_virtual_card(self, card):
  3616. # type:(UserVirtualCard)->None
  3617. return self.update(boundVirtualCardId = card.id)
  3618. def unbind_virtual_card(self, card):
  3619. # type:(UserVirtualCard)->None
  3620. """
  3621. 以后也许会支持多张卡
  3622. :param card:
  3623. :return:
  3624. """
  3625. return self.update(boundVirtualCardId = None)
  3626. # 这个那种有余额的卡,才会调用
  3627. @staticmethod
  3628. def update_balance(cardId, balance):
  3629. card = Card.objects(id=cardId).get()
  3630. result = card.update(balance = balance)
  3631. if not result:
  3632. logger.error('update error, cardId=%s, balance=%s' % (cardId, balance))
  3633. return result
  3634. @staticmethod
  3635. def check_card_no(cardNo):
  3636. """
  3637. 检查实体卡卡号的合法性
  3638. :param cardNo:
  3639. :return:
  3640. """
  3641. if CARD_NO_RE.match(cardNo):
  3642. return True
  3643. return False
  3644. @classmethod
  3645. def check_swap_card_no(cls, cardNo, dealerId, agentId):
  3646. try:
  3647. card = cls.objects.get(cardNo=cardNo, agentId=agentId)
  3648. except DoesNotExist:
  3649. return True, ""
  3650. if card.dealerId and card.dealerId != dealerId:
  3651. return False, u"该卡已经被其他经销商绑定,请确认卡号无误"
  3652. if card.openId:
  3653. return False, u"该卡号已经被其他用户绑定,请确认卡号无误"
  3654. if CardConsumeRecord.objects.filter(cardId=str(card.id)):
  3655. return False, u"该卡号存在用户使用记录, 请确认卡号无误"
  3656. card.delete()
  3657. return True, ""
  3658. @staticmethod
  3659. def record_dev_card_no(devNo, cardNo):
  3660. serviceCache.set('%s-cardno' % devNo, cardNo, 2 * 60)
  3661. @staticmethod
  3662. def get_dev_card_no(devNo):
  3663. return serviceCache.get('%s-cardno' % devNo, None)
  3664. @staticmethod
  3665. def clear_dev_card_no(devNo):
  3666. serviceCache.delete('%s-cardno' % devNo)
  3667. def to_dict(self):
  3668. data = {
  3669. "cardId": str(self.id),
  3670. "cardNo": self.cardNo,
  3671. "cardName": self.cardName,
  3672. "phone": self.phone,
  3673. "cardType": self.cardType,
  3674. "remarks": self.remarks,
  3675. "frozen": self.frozen,
  3676. "balance": self.balance,
  3677. "lastMaxBalance": self.lastMaxBalance,
  3678. "bindStatus": self.isBinded,
  3679. }
  3680. return data
  3681. def clear_card(self):
  3682. return self.update(
  3683. cardType = '',
  3684. openId = '',
  3685. nickName = '',
  3686. productAgentId = '',
  3687. cardName = '',
  3688. phone = '',
  3689. managerialAppId = '',
  3690. managerialOpenId = '',
  3691. dealerId = '',
  3692. groupId = '',
  3693. remarks = '',
  3694. isHaveBalance = True,
  3695. lastMaxBalance = RMB('0.0'),
  3696. devNo = '',
  3697. devTypeCode = '',
  3698. frozen = False,
  3699. status = 'active',
  3700. boundVirtualCardId = None,
  3701. attachParas = {}
  3702. )
  3703. class RefundMoneyRecord(Searchable):
  3704. """
  3705. 用户退款记录
  3706. """
  3707. class Status(object):
  3708. """ 退款单的状态 正常流程顺序是从上至下 """
  3709. CREATED = 'created' # 创建
  3710. PROCESSING = 'processing' # 申请中
  3711. FAILURE = 'failure' # 申请失败 (彻底失败 需要重新发起)
  3712. SUCCESS = 'success' # 退款成功 (异步结果)
  3713. CLOSED = 'closed' # 退款失败 (异步结果 不需要重新发起)
  3714. # 订单常见的状态
  3715. # created ---> processing ---> success (申请 然后接受成功)
  3716. # created ---> failure (直接申请失败)
  3717. # created ---> processing ---> closed (申请成功 回调失败)
  3718. # created ---> processing ---> failure (申请成功 回调失败 不可以重试)
  3719. rechargeObjId = ObjectIdField(verbose_name = u'对应充值单')
  3720. # refundSeq = IntField(verbose_name=u'多次退款,计算退款序列号', default=1)
  3721. orderNo = StringField(verbose_name = u'商户退款订单号', default = '')
  3722. errorCode = StringField(verbose_name = u"错误代码", default = "")
  3723. errorDesc = StringField(verbose_name = u"错误描述", default = "")
  3724. money = MonetaryField(verbose_name = u"退款的金钱数额", default = RMB('0.00'))
  3725. coins = MonetaryField(verbose_name = u"清理用户金币数", default = VirtualCoin('0.00'))
  3726. status = StringField(verbose_name = u'订单状态', default = Status.CREATED)
  3727. datetimeAdded = DateTimeField(verbose_name = u"退款创建时间", default = datetime.datetime.now)
  3728. datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
  3729. finishedTime = DateTimeField(verbose_name = u"退款到账时间")
  3730. tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = "")
  3731. meta = {
  3732. "collection": "RefundMoneyRecord",
  3733. "db_alias": "default",
  3734. }
  3735. @classmethod
  3736. def issue(cls, order, refundCash, deductCoins):
  3737. # type:(RechargeRecord, RMB, VirtualCoin)->RefundMoneyRecord
  3738. refund_order_no = OrderNoMaker.make_order_no_32(
  3739. identifier = order.logicalCode,
  3740. main_type = OrderMainType.REFUND,
  3741. sub_type = RefundSubType.REFUND)
  3742. return cls(
  3743. rechargeObjId = order.id,
  3744. # refundSeq=next_seq,
  3745. orderNo = refund_order_no,
  3746. money = refundCash,
  3747. coins = deductCoins,
  3748. status = cls.Status.PROCESSING).save()
  3749. def succeed(self, tradeRefundNo, finishedTime): # type:(str, datetime.datetime) -> bool
  3750. """ 更新为成功状态 已经成功退款 """
  3751. result = self.__class__.get_collection().update_one(
  3752. {
  3753. 'orderNo': self.orderNo,
  3754. 'status': self.Status.PROCESSING
  3755. },
  3756. {
  3757. '$set': {
  3758. 'status': self.Status.SUCCESS,
  3759. 'datetimeUpdated': datetime.datetime.now(),
  3760. 'tradeRefundNo': tradeRefundNo,
  3761. 'finishedTime': finishedTime
  3762. }
  3763. }
  3764. )
  3765. return result.matched_count == 1
  3766. def fail(self, errorCode="", errorDesc=""): # type:(str, unicode) -> bool
  3767. """
  3768. 更新为失败状态 表示退款申请失败 这种情况下 可能就需要售后进行介入
  3769. 1. 订单申请发起时候即失败
  3770. 2. 订单申请通过 但是回调的时候明确失败 并且是不可重试的失败
  3771. 3. 此状态即表示订单没退款 理论上可以重新发起退款
  3772. """
  3773. result = self.__class__.objects.filter(
  3774. orderNo=self.orderNo,
  3775. status__in=[self.Status.PROCESSING, self.Status.CREATED]
  3776. ).update(
  3777. status=self.Status.FAILURE,
  3778. datetimeUpdated=datetime.datetime.now(),
  3779. errorCode=errorCode,
  3780. errorDesc=errorDesc
  3781. )
  3782. # TODO 这种情况需不需要将对于经销商的分账重新执行
  3783. return result
  3784. def closed(self, tradeRefundNo, errorCode="", errorDesc=""): # type:(str, str, str) -> bool
  3785. """
  3786. 退款申请成功了 表示合法的退款申请 但是由于某些原因业务进行不下去
  3787. """
  3788. result = self.__class__.objects.filter(
  3789. orderNo=self.orderNo,
  3790. status=self.Status.PROCESSING
  3791. ).update(
  3792. status=self.Status.CLOSED,
  3793. datetimeUpdated=datetime.datetime.now(),
  3794. tradeRefundNo=tradeRefundNo,
  3795. errorCode=errorCode,
  3796. errorDesc=errorDesc
  3797. )
  3798. return result
  3799. def processing(self): # type:() -> bool
  3800. result = self.__class__.objects.filter(
  3801. orderNo=self.orderNo,
  3802. status__in=[self.Status.CLOSED, self.Status.CREATED, self.Status.FAILURE]
  3803. ).update(
  3804. status=self.Status.PROCESSING,
  3805. datetimeUpdated=datetime.datetime.now()
  3806. )
  3807. return result
  3808. @property
  3809. def is_fail(self):
  3810. """ 是否订单失败 """
  3811. return self.status == self.Status.FAILURE
  3812. @property
  3813. def is_closed(self):
  3814. """ 退款单是否已经关闭 目前这个状态没有用 适用于用户手动取消退款申请 """
  3815. return self.status == self.Status.CLOSED
  3816. @property
  3817. def is_success(self):
  3818. """ 退款已经成功 """
  3819. return self.status == self.Status.SUCCESS
  3820. @property
  3821. def is_created(self):
  3822. return self.status == self.Status.CREATED
  3823. @property
  3824. def is_apply(self):
  3825. """ 是否已经发出退款申请 """
  3826. return self.status in [self.Status.CREATED, self.Status.PROCESSING]
  3827. @property
  3828. def is_processing(self):
  3829. return self.status == self.Status.PROCESSING
  3830. @property
  3831. def is_successful(self):
  3832. """ 保留旧的方法 """
  3833. return self.status in [self.Status.SUCCESS, self.Status.PROCESSING]
  3834. @property
  3835. def pay_sub_order(self):
  3836. # type: ()->RechargeRecord
  3837. if not hasattr(self, '__pay_sub_order__'):
  3838. pay_order = RechargeRecord.objects(id = str(self.rechargeObjId)).first()
  3839. setattr(self, '__pay_sub_order__', pay_order)
  3840. return getattr(self, '__pay_sub_order__')
  3841. @pay_sub_order.setter
  3842. def pay_sub_order(self, order):
  3843. setattr(self, '__pay_sub_order__', order)
  3844. @property
  3845. def refund_order_record(self):
  3846. if not hasattr(self, '__refund_order_record__'):
  3847. order = RechargeRecord.objects(orderNo = self.orderNo).first()
  3848. setattr(self, '__refund_order_record__', order)
  3849. return getattr(self, '__refund_order_record__')
  3850. @refund_order_record.setter
  3851. def refund_order_record(self, refund_order_record):
  3852. setattr(self, '__refund_order_record__', refund_order_record)
  3853. @classmethod
  3854. def get_record(cls, order_no):
  3855. return cls.objects(orderNo = order_no).first()
  3856. @property
  3857. def user(self):
  3858. # type: ()->MyUser
  3859. return self.pay_sub_order.user
  3860. class VCardConsumeRecord(OrderRecordBase):
  3861. orderNo = StringField(verbose_name = "订单号", default = "")
  3862. cardId = StringField(verbose_name = "实体卡ID", default = "")
  3863. dealerId = StringField(verbose_name = "经销商ID", default = "")
  3864. # : 在部分场合,设备会失败启动,但这时应该添加该字段为False方便追溯
  3865. remarks = StringField(verbose_name = "备注", default = "")
  3866. desc = StringField(verbose_name = "描述", default = "")
  3867. finishedTime = DateTimeField(verbose_name = '结束时间', default = datetime.datetime.now)
  3868. servicedInfo = DictField(verbose_name = '服务内容', default = {})
  3869. attachParas = DictField(verbose_name = '服务内容', default = {})
  3870. # 本次消费消耗的额度
  3871. consumeData = DictField(verbose_name = '消耗的额度', default = {})
  3872. # 日限额消耗的额度
  3873. consumeDayData = DictField(verbose_name = '消耗的额度', default = {})
  3874. meta = {
  3875. "collection": "VCardConsumeRecord",
  3876. "db_alias": "default",
  3877. }
  3878. @staticmethod
  3879. def make_no(identifier):
  3880. # time:14;main_type:1;sub_type:1;identifier:15;extra:5
  3881. return '{time}{main_type}{sub_type}{identifier}{reserved}'.format(
  3882. time = datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
  3883. main_type = OrderMainType.CONSUME,
  3884. sub_type = UserConsumeSubType.VIRTUAL_CARD,
  3885. identifier = '{:0>15}'.format(identifier),
  3886. reserved = get_random_str(5, string.digits + string.uppercase))
  3887. @staticmethod
  3888. def paginate(cardId, pageIndex, pageSize):
  3889. records = VCardConsumeRecord.objects.filter(cardId = cardId) \
  3890. .order_by('-dateTimeAdded').skip((pageIndex - 1) * pageSize).limit(pageSize)
  3891. if not records:
  3892. return 0, []
  3893. dataList = []
  3894. for record in records:
  3895. newData = {
  3896. 'id': str(record.id),
  3897. 'createdTime': record.dateTimeAdded.strftime(Const.DATETIME_FMT),
  3898. 'address': record.address,
  3899. 'devNo': record.devNo,
  3900. 'openId': record.openId,
  3901. 'nickname': record.nickname,
  3902. 'logicalCode': record.logicalCode,
  3903. 'groupNumber': record.groupNumber,
  3904. 'groupName': record.groupName,
  3905. 'unit': record.consumeData.get("unit", ""),
  3906. 'devTypeCode': record.devTypeCode,
  3907. 'devTypeName': record.dev_type_name,
  3908. 'isNormal': True,
  3909. 'ownerId': record.dealerId
  3910. }
  3911. amount = record.consumeData.get("count", "")
  3912. if amount:
  3913. amount = Quantity(amount)
  3914. newData.update({'amount': amount})
  3915. if record.servicedInfo:
  3916. newData.update(record.servicedInfo)
  3917. dataList.append(newData)
  3918. return len(dataList), dataList
  3919. @property
  3920. def amount(self):
  3921. _amount = self.consumeData.get("count", None)
  3922. if _amount is not None:
  3923. return str(Quantity(_amount))
  3924. else:
  3925. return ""
  3926. @property
  3927. def unit(self):
  3928. return self.consumeData.get("unit", "")
  3929. @property
  3930. def created_date(self):
  3931. return self.dateTimeAdded
  3932. @property
  3933. def completion_date(self):
  3934. return self.finishedTime
  3935. @property
  3936. def device_start_time(self):
  3937. return self.dateTimeAdded
  3938. @property
  3939. def device_finished_time(self):
  3940. return self.finishedTime
  3941. def to_user_detail(self):
  3942. """
  3943. 消费记录对于用户端显示的详细信息
  3944. :return:
  3945. """
  3946. data = {
  3947. 'id': str(self.id),
  3948. 'orderNo': self.orderNo,
  3949. 'createdTime': self.dateTimeAdded.strftime(Const.DATETIME_FMT),
  3950. 'deviceStatTime': self.device_start_time,
  3951. 'address': self.address,
  3952. 'devNo': self.devNo,
  3953. 'logicalCode': self.logicalCode,
  3954. 'groupNumber': self.groupNumber,
  3955. 'groupName': self.groupName,
  3956. 'devTypeCode': self.devTypeCode,
  3957. 'devTypeName': self.dev_type_name,
  3958. 'isNormal': True,
  3959. 'amount': self.amount,
  3960. 'unit': self.unit,
  3961. 'ownerId': self.dealerId,
  3962. 'consumeType': 'mobile_vcard',
  3963. 'openId': self.openId,
  3964. 'userNickname': self.nickname
  3965. }
  3966. # 兼容之前的订单 凡是没有订单状态机的订单,一律视为 之前的订单
  3967. if hasattr(self, "state") and self.state:
  3968. data.update({"orderStatus": self.state})
  3969. if self.servicedInfo:
  3970. data.update(self.servicedInfo)
  3971. return data
  3972. @property
  3973. def ownerId(self):
  3974. return self.dealerId
  3975. @property
  3976. def owner(self):
  3977. from apps.web.dealer.models import Dealer
  3978. _attr_name = '__my_owner__'
  3979. if hasattr(self, _attr_name):
  3980. return getattr(self, _attr_name)
  3981. else:
  3982. if not self.dealerId:
  3983. return None
  3984. else:
  3985. dealer = Dealer.objects(id = self.dealerId).first()
  3986. setattr(self, _attr_name, dealer)
  3987. return dealer
  3988. @property
  3989. def summary(self):
  3990. return {
  3991. 'id': str(self.id),
  3992. 'consumeType': 'mobile_vcard',
  3993. 'ownerId': self.ownerId,
  3994. 'logicalCode': self.logicalCode,
  3995. 'groupName': self.groupName,
  3996. 'devTypeName': self.dev_type_name,
  3997. 'amount': self.amount,
  3998. 'unit': self.unit,
  3999. 'createdTime': self.dateTimeAdded.strftime(Const.DATETIME_FMT),
  4000. }
  4001. VirtualCardQuotaList = List[Dict[str, Union[float, unicode]]]
  4002. class UserVirtualCard(Searchable):
  4003. cardNo = StringField(verbose_name = "卡编号", default = "")
  4004. cardTypeId = StringField(verbose_name = u'卡的类型ID', default = '') # 如果卡停止销售了,这里就可以不允许续充
  4005. openIds = ListField(verbose_name = "绑定的用户") # 里面放的是MyUser的Id
  4006. cardName = StringField(verbose_name = "卡名称", default = "")
  4007. ownerOpenId = StringField(verbose_name = "卡的所有者", default = "")
  4008. nickname = StringField(verbose_name = "昵称", default = "")
  4009. logicalCode = StringField(verbose_name = u'发卡的时候记录的设备编码', default = '')
  4010. groupId = StringField(verbose_name = u'发卡的时候记录的地址', default = '')
  4011. dealerId = StringField(verbose_name = "卡的发布老板", default = "")
  4012. groupIds = ListField(verbose_name = "可用使用卡的地址", default = []) # 空表示没有地址,*表示所有地址下可用
  4013. devTypeList = ListField(verbose_name = "设备类型清单", default = [])
  4014. price = MonetaryField(verbose_name = "卡的售价", default = RMB('0.00'))
  4015. periodDays = IntField(verbose_name = "卡的可用天数", default = 30)
  4016. expiredTime = DateTimeField(verbose_name = "过期时间",
  4017. default = lambda: datetime.datetime.now() + datetime.timedelta(days = 365))
  4018. userLimit = IntField(verbose_name = "用户数量限制", default = 10)
  4019. userDesc = StringField(verbose_name = "描述", default = "")
  4020. dayQuota = ListField(verbose_name = "日限额度", default = []) # {'unit':u'次','count':2}
  4021. quota = ListField(verbose_name = "总的额度", default = [])
  4022. frozenQuota = ListField(verbose_name = u'冻结额度', default = [])
  4023. startTime = DateTimeField(verbose_name = "买卡的时间", default = datetime.datetime.now)
  4024. dayUsed = DictField(verbose_name = "日消耗情况", default = {}) # {'2018-12-26':[{'unit':'次','count':3}]}
  4025. quotaUsed = ListField(verbose_name = "总的消耗情况", default = []) # [{'unit':'次','count':3}]
  4026. status = StringField(verbose_name = "卡状态", default = 'normal')
  4027. # expired:过期,耗尽:exhausted, 未激活:nonactivated(经销商开虚拟卡),used(经销商开卡解绑了,并未过期)
  4028. remark = StringField(verbose_name = "卡片备注", default = "")
  4029. continueTime = DateTimeField(verbose_name="买卡的时间", default = None) # 接续时间,用于续费后,下一次卡接续开始
  4030. ongoingList = ListField(DictField(), verbose = u'冻结的配额')
  4031. managerialAppId = StringField(verbose_name = "管理公众号AppId", default = None)
  4032. managerialOpenId = StringField(verbose_name = "管理openId", default = None)
  4033. # 坤元虚拟卡特有:功率
  4034. power = IntField(verbose_name="包月卡功率限制", default=0)
  4035. meta = {
  4036. "collection": "UserVirtualCard",
  4037. "db_alias": "default",
  4038. }
  4039. search_fields = ('nickname', 'cardNo', 'cardName')
  4040. REVERSE_DAY = 3
  4041. @property
  4042. def payVia(self):
  4043. return "virtualCard"
  4044. @staticmethod
  4045. def make_no():
  4046. timestamp = generate_timestamp_ex()
  4047. random_int = random.randint(1, 1000)
  4048. return "%d%03d" % (timestamp, random_int)
  4049. # --------------------------------------------------------------- 所有的虚拟卡的新方法 需要兼容以前 一定需要严格测试
  4050. @staticmethod
  4051. def find_match_unit(quotaList, package):
  4052. """
  4053. 从额度列表中找到符合套餐单位的 返回index
  4054. :param quotaList: [{"unit": "次", "count": 15}, {"unit": "分钟", "count": 600}]
  4055. :param package: {"unit": "分钟", "time": 30}
  4056. """
  4057. # 只有一种额度的 走之前的流程 不要影响客户的使用
  4058. if len(quotaList) == 1:
  4059. return UserVirtualCard._find_match_unit(quotaList, package)
  4060. # 多单位的情况下 不再对单位进行转换 如果单位不匹配 就直接跳过
  4061. pCount, pUnit = package["time"], package["unit"]
  4062. for _index, _quota in enumerate(quotaList):
  4063. if _quota["unit"] != pUnit or float(_quota["count"]) < float(pCount):
  4064. continue
  4065. ii = _index
  4066. break
  4067. else:
  4068. ii = -1
  4069. return ii
  4070. @staticmethod
  4071. def calc_left_quota(quotaList, usedList):
  4072. """
  4073. 计算剩余的额度
  4074. :param quotaList: 总的额度列表
  4075. :param usedList: 已经使用额度情况
  4076. """
  4077. # TODO 目前使用双循环解决,其实可以加一个单位排序,一次遍历搞定 时间复杂度将会降低一个量级
  4078. leftQuotaList = list()
  4079. for _quota in quotaList:
  4080. _qCount, _qUnit = float(_quota["count"]), _quota["unit"]
  4081. for _used in usedList:
  4082. # 单位有匹配的 则直接计算额度
  4083. if _used["unit"] == _qUnit:
  4084. _usedQuota = {"count": float(max(_qCount-_used["count"], 0)), "unit": _qUnit}
  4085. break
  4086. else:
  4087. continue
  4088. # 没有找到这种单位 说明这种额度没有使用过 则剩余额度为满值
  4089. else:
  4090. _usedQuota = {"count": float(_qCount), "unit": _qUnit}
  4091. leftQuotaList.append(_usedQuota)
  4092. return leftQuotaList
  4093. def calc_left_total_quota(self):
  4094. """
  4095. 计算剩余的总额度 将所有的冻结额度都算上使用的
  4096. :return:
  4097. """
  4098. usedMap, frozenMap = dict(), dict()
  4099. for _item in self.quotaUsed:
  4100. if _item["unit"] in usedMap:
  4101. usedMap[_item["unit"]] += float(_item["count"])
  4102. else:
  4103. usedMap[_item["unit"]] = float(_item["count"])
  4104. for _on in self.ongoingList:
  4105. if _on["quota"]["unit"] in frozenMap:
  4106. frozenMap[_on["quota"]["unit"]] += float(_on["quota"]["count"])
  4107. else:
  4108. frozenMap[_on["quota"]["unit"]] = float(_on["quota"]["count"])
  4109. quotaList = self.quota
  4110. usedList = [{"count": _v, "unit": _k} for _k, _v in dict(Counter(usedMap)+Counter(frozenMap)).items()]
  4111. return self.calc_left_quota(quotaList, usedList)
  4112. def calc_left_day_quota(self, dayKey):
  4113. """
  4114. 计算当天剩下的总额度 注意key的匹配 将所有的当天的额度都算上使用的
  4115. :param dayKey:
  4116. :return:
  4117. """
  4118. usedMap, frozenMap = dict(), dict()
  4119. for _item in self.dayUsed.get(dayKey, list()):
  4120. if _item["unit"] in usedMap:
  4121. usedMap[_item["unit"]] += _item["count"]
  4122. else:
  4123. usedMap[_item["unit"]] = _item["count"]
  4124. for _on in self.ongoingList:
  4125. if _on["dayKey"] != dayKey:
  4126. continue
  4127. if _on["dayQuota"]["unit"] in frozenMap:
  4128. frozenMap[_on["dayQuota"]["unit"]] += _on["dayQuota"]["count"]
  4129. else:
  4130. frozenMap[_on["dayQuota"]["unit"]] = _on["dayQuota"]["count"]
  4131. quotaList = self.dayQuota
  4132. usedList = [{"count": _v, "unit": _k} for _k, _v in dict(Counter(usedMap) + Counter(frozenMap)).items()]
  4133. return self.calc_left_quota(quotaList, usedList)
  4134. # --------------------------------------------------------------- 新方法结束
  4135. @staticmethod
  4136. def _find_match_unit(quotaList, package):
  4137. # type:(VirtualCardQuotaList, dict)->int
  4138. match = False
  4139. ii = 0
  4140. for t1Dict in quotaList:
  4141. unit = t1Dict['unit']
  4142. count = t1Dict['count']
  4143. if unit == u'次' and count >= 1:
  4144. match = True
  4145. break
  4146. elif (unit in [u'分钟', u'小时', u'天']) and (package['unit'] in [u'分钟', u'小时', u'天']):
  4147. if unit == u'小时':
  4148. t1Count = count * 60
  4149. elif unit == u'天':
  4150. t1Count = count * 60 * 24
  4151. else:
  4152. t1Count = count
  4153. if package['unit'] == u'小时':
  4154. pCount = float(package['time']) * 60
  4155. elif package['unit'] == u'天':
  4156. pCount = float(package['time']) * 60 * 24
  4157. else:
  4158. pCount = float(package['time'])
  4159. if t1Count >= pCount:
  4160. match = True
  4161. break
  4162. elif (unit in [u'分钟', u'小时', u'天']) and package['unit'] == u'度': # 直接按照12小时计算
  4163. if unit == u'小时':
  4164. t1Count = count * 60
  4165. elif unit == u'天':
  4166. t1Count = count * 60 * 24
  4167. else:
  4168. t1Count = count
  4169. if t1Count >= 6.0 * 60: # 6小时打底
  4170. match = True
  4171. break
  4172. elif unit == u'币' and count >= package['coins']:
  4173. match = True
  4174. break
  4175. elif unit == u'度' and package['unit'] == u'度' and count >= package['time']:
  4176. match = True
  4177. break
  4178. elif unit == u'度' and package['unit'] != u'度' and count >= 3.0:
  4179. match = True
  4180. break
  4181. elif package['unit'] == u'次':
  4182. if unit == u'分钟' and count >= 360.0:
  4183. match = True
  4184. break
  4185. elif unit == u'小时' and count >= 6.0:
  4186. match = True
  4187. break
  4188. elif unit == u'天' and count >= 0.25:
  4189. match = True
  4190. break
  4191. elif unit == u'度' and count >= 3.0: # 默认写死3度电,多退少补
  4192. match = True
  4193. break
  4194. elif unit == u'币' and count >= package['coins']:
  4195. match = True
  4196. break
  4197. ii += 1
  4198. return ii if match else -1
  4199. @staticmethod
  4200. def find_match_unit_and_can_use_count(quotaList, package):
  4201. """
  4202. 找出虚拟卡能否使用多少次 某些设备的特殊需求 例如和动
  4203. """
  4204. # 兼容之前的
  4205. if len(quotaList) == 1:
  4206. usage_count = 0
  4207. for t1Dict in quotaList:
  4208. unit = t1Dict['unit']
  4209. count = t1Dict['count']
  4210. if unit == u'次' and count >= 1:
  4211. usage_count = count
  4212. break
  4213. elif (unit in [u'分钟', u'小时', u'天']) and (package['unit'] in [u'分钟', u'小时', u'天']):
  4214. if unit == u'小时':
  4215. t1Count = count * 60
  4216. elif unit == u'天':
  4217. t1Count = count * 60 * 24
  4218. else:
  4219. t1Count = count
  4220. if package['unit'] == u'小时':
  4221. pCount = float(package['time']) * 60
  4222. elif package['unit'] == u'天':
  4223. pCount = float(package['time']) * 60 * 24
  4224. else:
  4225. pCount = float(package['time'])
  4226. if t1Count >= pCount:
  4227. usage_count = t1Count // pCount
  4228. break
  4229. elif (unit in [u'分钟', u'小时', u'天']) and package['unit'] == u'度': # 直接按照12小时计算
  4230. if unit == u'小时':
  4231. t1Count = count * 60
  4232. elif unit == u'天':
  4233. t1Count = count * 60 * 24
  4234. else:
  4235. t1Count = count
  4236. if t1Count >= 6.0 * 60: # 6小时打底
  4237. usage_count = t1Count // 6.0 * 60
  4238. break
  4239. elif unit == u'币' and count >= package['coins']:
  4240. usage_count = count // float(package['coins'])
  4241. break
  4242. elif unit == u'度' and package['unit'] == u'度' and count >= package['time']:
  4243. usage_count = count // float(package['time'])
  4244. break
  4245. elif unit == u'度' and package['unit'] != u'度' and count >= 3.0:
  4246. usage_count = count // 3
  4247. break
  4248. elif package['unit'] == u'次':
  4249. if unit == u'分钟' and count >= 360.0:
  4250. usage_count = count // 360.0
  4251. break
  4252. elif unit == u'小时' and count >= 6.0:
  4253. usage_count = count // 6
  4254. break
  4255. elif unit == u'天' and count >= 0.25:
  4256. usage_count = count // 0.25
  4257. break
  4258. elif unit == u'度' and count >= 3.0: # 默认写死3度电,多退少补
  4259. usage_count = count // 3
  4260. break
  4261. elif unit == u'币' and count >= package['coins']:
  4262. usage_count = count // float(package['coins'])
  4263. break
  4264. return int(usage_count)
  4265. # 两种单位的
  4266. else:
  4267. pCount, pUnit = package["time"], package["unit"]
  4268. for _index, _quota in enumerate(quotaList):
  4269. # 单位不一致 或者数量不够的 直接跳过
  4270. if _quota["unit"] != pUnit or float(_quota["count"]) < float(pCount):
  4271. continue
  4272. # 然后求出count
  4273. if pUnit == u"次":
  4274. return _quota["count"]
  4275. # 时间的 由于单位一样 直接除法
  4276. else:
  4277. return _quota["count"] // pCount
  4278. else:
  4279. return 0
  4280. @staticmethod
  4281. def count_by_unit(unit, package):
  4282. # type:(unicode, dict)->Union[float, int]
  4283. """
  4284. 根据单位,计算消耗量
  4285. 根据套餐计算以及单位,计算需要扣除的指标数量。比如单位是:币,那就直接看套餐多少币,就需要扣除多少币
  4286. :param unit:
  4287. :param package:
  4288. :return:
  4289. """
  4290. if unit == u'次':
  4291. return 1
  4292. elif unit == u'币':
  4293. return package['coins']
  4294. elif unit == u'分钟':
  4295. if package['unit'] == u'分钟':
  4296. return float(package['time'])
  4297. elif package['unit'] == u'小时':
  4298. return float(package['time']) * 60.0
  4299. elif package['unit'] == u'天':
  4300. return float(package['time']) * 60 * 24.0
  4301. else:
  4302. return 6.0 * 60
  4303. elif unit == u'小时':
  4304. if package['unit'] == u'分钟':
  4305. return float(package['time']) / 60.0
  4306. elif package['unit'] == u'小时':
  4307. return float(package['time'])
  4308. elif package['unit'] == u'天':
  4309. return float(package['time']) * 24.0
  4310. else:
  4311. return 6.0
  4312. elif unit == u'天':
  4313. if package['unit'] == u'分钟':
  4314. return float(package['time']) / 360.0
  4315. elif package['unit'] == u'小时':
  4316. return float(package['time']) / 24.0
  4317. elif package['unit'] == u'天':
  4318. return float(package['time'])
  4319. else:
  4320. return 0.25
  4321. elif unit == u'度':
  4322. if package['unit'] == u'度':
  4323. return float(package['time'])
  4324. else:
  4325. return 3.0
  4326. else:
  4327. raise ValueError(u'does not support unit %s' % (unit,))
  4328. def can_use_today(self, package):
  4329. # type:(dict)->bool
  4330. """
  4331. 检查今天是否可以用卡
  4332. :param package:
  4333. :return:
  4334. """
  4335. if package.get("isTempPackage"):
  4336. return False
  4337. # 非正常状态的卡都不让用
  4338. if self.status not in ["normal", "warning"]:
  4339. return False
  4340. # 先计算出总的剩余额度,以及每日的剩余额度
  4341. leftQuota = self.calc_left_total_quota()
  4342. dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
  4343. leftDayQuota = self.calc_left_day_quota(dayKey)
  4344. # 剩余总额度以及剩余日限额度检查没有问题,就直接返回
  4345. if UserVirtualCard.find_match_unit(leftQuota, package) >= 0 and UserVirtualCard.find_match_unit(leftDayQuota, package) >= 0:
  4346. return True
  4347. return False
  4348. def freeze_quota(self, package, transaction_id): # type:(dict, str) -> bool
  4349. """
  4350. 消费一次,包括扣掉总额,增加消费记录
  4351. :param package:
  4352. :param transaction_id: 交易的ID
  4353. :return:
  4354. """
  4355. try:
  4356. # 找出额度单元,然后扣掉
  4357. leftQuota = self.calc_left_total_quota()
  4358. dayKey = today_format_str()
  4359. leftDayQuota = self.calc_left_day_quota(dayKey)
  4360. allIndex = UserVirtualCard.find_match_unit(leftQuota, package)
  4361. dayIndex = UserVirtualCard.find_match_unit(leftDayQuota, package)
  4362. if allIndex < 0 or dayIndex < 0:
  4363. raise InsufficientFundsError(u'虚拟卡配额不足')
  4364. # 先处理总额度
  4365. unit = leftQuota[allIndex]['unit']
  4366. consume = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
  4367. # 处理每日额度
  4368. unit = leftDayQuota[dayIndex]['unit']
  4369. day_consume = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
  4370. query = {'_id': self.id,
  4371. 'ongoingList.transaction_id': {'$ne': transaction_id}}
  4372. update = {
  4373. '$addToSet': {'ongoingList': {
  4374. 'transaction_id': transaction_id,
  4375. 'quota': consume,
  4376. 'dayQuota': day_consume,
  4377. 'dayKey': dayKey
  4378. }}}
  4379. result = self.get_collection().update_one(filter=query, update=update, upsert = False)
  4380. return bool(result.modified_count == 1)
  4381. finally:
  4382. try:
  4383. day3Key = (datetime.datetime.now() - datetime.timedelta(days = self.REVERSE_DAY)).strftime('%Y-%m-%d')
  4384. for dk in self.dayUsed.keys():
  4385. if dk <= day3Key:
  4386. self.dayUsed.pop(dk)
  4387. self.save()
  4388. except Exception as e:
  4389. logger.exception('save user vcard e={}'.format(e))
  4390. def clear_frozen_quota(self, transaction_id, usedTime, spendElec, backCoins):
  4391. """
  4392. 清除冻结的额度,并按照一定的比例退还
  4393. """
  4394. def calc_left_by_unit(count, unit, ut, ue, uc):
  4395. if unit in [u'分钟', u'小时', u'天']:
  4396. if unit == u'分钟':
  4397. ut = int(ut)
  4398. elif unit == u'小时':
  4399. ut = round(ut / 60.0, 2)
  4400. else:
  4401. ut = round(ut / (60.0 * 24.0), 2)
  4402. leftCount = count - ut
  4403. elif unit in [u'度']:
  4404. leftCount = count - ue
  4405. elif unit in [u'次']: # 如果没有用,比如没有插电,一样,不能扣除配额
  4406. if int(ut) == 0 and int(ue) == 0:
  4407. leftCount = count
  4408. else:
  4409. leftCount = 0
  4410. elif unit in [u'币']: # 注意,这个是已经算好的退币数额
  4411. leftCount = uc
  4412. else:
  4413. leftCount = 0
  4414. return max(leftCount, 0)
  4415. frozen_item = None
  4416. for item in self.ongoingList:
  4417. if item['transaction_id'] == transaction_id:
  4418. frozen_item = item
  4419. break
  4420. if not frozen_item:
  4421. logger.debug('not find this frozen item. card = {}, consume id = {}'.format(repr(self), transaction_id))
  4422. return False, None, None
  4423. consumeTotal = frozen_item['quota']
  4424. consumeTotalLeft = calc_left_by_unit(consumeTotal['count'], consumeTotal['unit'], usedTime, spendElec,
  4425. backCoins)
  4426. consumeTotal['count'] -= float(consumeTotalLeft)
  4427. consumeDay = frozen_item['dayQuota']
  4428. consumeDayLeft = calc_left_by_unit(consumeDay['count'], consumeDay['unit'],
  4429. usedTime, spendElec, backCoins)
  4430. consumeDay['count'] -= float(consumeDayLeft)
  4431. day_key = frozen_item['dayKey']
  4432. clean_day_key = (datetime.datetime.now() - datetime.timedelta(days = self.REVERSE_DAY)).strftime('%Y-%m-%d')
  4433. if day_key <= clean_day_key:
  4434. match_quota_used = None
  4435. for item in self.quotaUsed:
  4436. if item['unit'] == consumeTotal['unit']:
  4437. match_quota_used = item
  4438. break
  4439. if not match_quota_used:
  4440. self.quotaUsed.append({'unit': consumeTotal['unit'], 'count': 0})
  4441. self.save()
  4442. query = {
  4443. '_id': self.id,
  4444. 'ongoingList': {'$elemMatch': {'transaction_id': transaction_id}},
  4445. 'quotaUsed.unit': consumeTotal['unit']
  4446. }
  4447. update = {
  4448. '$inc': {
  4449. 'quotaUsed.$.count': consumeTotal['count']
  4450. },
  4451. '$pull': {'ongoingList': {'transaction_id': transaction_id}},
  4452. }
  4453. result = self.get_collection().update_one(
  4454. filter = query,
  4455. update = update,
  4456. upsert = False
  4457. )
  4458. modified = bool(result.modified_count == 1)
  4459. return modified, consumeTotal, consumeDay
  4460. else:
  4461. match_quota_used = None
  4462. for item in self.quotaUsed:
  4463. if item['unit'] == consumeTotal['unit']:
  4464. match_quota_used = item
  4465. break
  4466. if not match_quota_used:
  4467. self.quotaUsed.append({'unit': consumeTotal['unit'], 'count': 0})
  4468. if day_key not in self.dayUsed:
  4469. self.dayUsed[day_key] = []
  4470. match_day_used = None
  4471. for item in self.dayUsed.get(day_key, []):
  4472. if item['unit'] == consumeDay['unit']:
  4473. match_day_used = item
  4474. break
  4475. if not match_day_used:
  4476. self.dayUsed[day_key].append({'unit': consumeDay['unit'], 'count': 0})
  4477. self.save()
  4478. query = {
  4479. '_id': self.id,
  4480. 'ongoingList': {'$elemMatch': {'transaction_id': transaction_id}},
  4481. 'quotaUsed.unit': consumeTotal['unit'],
  4482. 'dayUsed.{}.unit'.format(day_key): consumeDay['unit']
  4483. }
  4484. update = {
  4485. '$inc': {
  4486. 'quotaUsed.$.count': consumeTotal['count'],
  4487. 'dayUsed.{}.$.count'.format(day_key): consumeDay['count']
  4488. },
  4489. '$pull': {'ongoingList': {'transaction_id': transaction_id}},
  4490. }
  4491. result = self.get_collection().update_one(
  4492. filter = query,
  4493. update = update,
  4494. upsert = False)
  4495. modified = bool(result.modified_count == 1)
  4496. self.reload()
  4497. return modified, consumeTotal, consumeDay
  4498. def recover_frozen_quota(self, transaction_id):
  4499. """
  4500. 清除虚拟卡的冻结额度 不退换
  4501. """
  4502. query = {'_id': self.id}
  4503. update = {
  4504. '$pull': {'ongoingList': {'transaction_id': transaction_id}},
  4505. }
  4506. result = self.get_collection().update_one(
  4507. filter = query,
  4508. update = update,
  4509. upsert = False
  4510. )
  4511. return bool(result.modified_count == 1)
  4512. def consume(self, openId, group, dev, package, attachParas, nickname='', orderNo=None):
  4513. """
  4514. 消费一次,包括扣掉总额,增加消费记录
  4515. :param openId:
  4516. :param group:
  4517. :param dev:
  4518. :param package:
  4519. :param attachParas:
  4520. :param nickname:
  4521. :param orderNo: 订单编号
  4522. :return:
  4523. """
  4524. # 找出额度单元,然后扣掉
  4525. leftQuota = self.calc_left_total_quota()
  4526. dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
  4527. leftDayQuota = self.calc_left_day_quota(dayKey)
  4528. allIndex = UserVirtualCard.find_match_unit(leftQuota, package)
  4529. dayIndex = UserVirtualCard.find_match_unit(leftDayQuota, package)
  4530. if allIndex < 0 or dayIndex < 0:
  4531. return None
  4532. # 先处理总额度
  4533. unit = leftQuota[allIndex]['unit']
  4534. consumeData = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
  4535. match = False
  4536. for tDict in self.quotaUsed:
  4537. if tDict['unit'] == unit:
  4538. tDict['count'] = float(tDict['count']) + float(consumeData['count'])
  4539. match = True
  4540. break
  4541. if not match:
  4542. self.quotaUsed.append(consumeData)
  4543. # 处理每日额度
  4544. unit = leftDayQuota[dayIndex]['unit']
  4545. consumeDayData = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
  4546. match = False
  4547. for tDict in self.dayUsed.get(dayKey, []):
  4548. if tDict['unit'] == unit:
  4549. consumeDayData = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
  4550. tDict['count'] = float(tDict['count']) + float(consumeDayData['count'])
  4551. match = True
  4552. break
  4553. if not match:
  4554. self.dayUsed[dayKey] = [consumeDayData]
  4555. # 清理掉3天以前的每日消费情况
  4556. day3Key = (datetime.datetime.now() - datetime.timedelta(days=3)).strftime('%Y-%m-%d')
  4557. for dk in self.dayUsed.keys():
  4558. if dk <= day3Key:
  4559. self.dayUsed.pop(dk)
  4560. try:
  4561. self.save()
  4562. except Exception as e:
  4563. logger.exception('[consume] save user vcard e={}'.format(e))
  4564. return None
  4565. # 增加一条消费记录
  4566. newRcd = VCardConsumeRecord(
  4567. orderNo = orderNo if orderNo is not None else VCardConsumeRecord.make_no(dev.logicalCode),
  4568. openId = openId,
  4569. nickname = nickname,
  4570. cardId = str(self.id),
  4571. dealerId = self.dealerId,
  4572. devNo = dev['devNo'],
  4573. devTypeCode = dev.devTypeCode,
  4574. devTypeName = dev.devTypeName,
  4575. logicalCode = dev['logicalCode'],
  4576. groupId = group['groupId'],
  4577. address = group['address'],
  4578. groupNumber = dev['groupNumber'],
  4579. groupName = group['groupName'],
  4580. attachParas = attachParas,
  4581. consumeData = consumeData,
  4582. consumeDayData = consumeDayData
  4583. )
  4584. try:
  4585. newRcd.save()
  4586. except Exception as e:
  4587. logger.error('[consume] save vcard consume e=%s' % e)
  4588. return None
  4589. return newRcd
  4590. def new_consume_record(self, dev, user, consumeTotal, attachParas=None, orderNo=None):
  4591. newRcd = VCardConsumeRecord(
  4592. orderNo=orderNo if orderNo is not None else VCardConsumeRecord.make_no(dev.logicalCode),
  4593. openId=user.openId,
  4594. nickname=user.nickname,
  4595. cardId=str(self.id),
  4596. dealerId=dev.ownerId,
  4597. devNo=dev.devNo,
  4598. devTypeCode = dev.devTypeCode,
  4599. devTypeName = dev.devTypeName,
  4600. logicalCode=dev.logicalCode,
  4601. groupId=dev.group.groupId,
  4602. address=dev.group.address,
  4603. groupNumber=dev.groupNumber,
  4604. groupName=dev.group.groupName,
  4605. attachParas=attachParas or dict(),
  4606. consumeData=consumeTotal,
  4607. consumeDayData=consumeTotal
  4608. )
  4609. try:
  4610. newRcd.save()
  4611. except Exception as e:
  4612. logger.error('[consume] save vcard consume e=%s' % e)
  4613. return None
  4614. return newRcd
  4615. def refund_quota(self, consumeRcd, usedTime, spendElec, backCoins):
  4616. """
  4617. 将虚拟卡的额度返回 对应的使用方法是consume
  4618. :param consumeRcd:
  4619. :param usedTime: 单位一定是分钟
  4620. :param spendElec: 单位一定是度
  4621. :param backCoins: 单位一定是金币
  4622. :return:
  4623. """
  4624. def calc_left_by_unit(count, unit, ut, ue, uc):
  4625. if unit in [u'分钟', u'小时', u'天']:
  4626. if unit == u'分钟':
  4627. ut = int(ut)
  4628. elif unit == u'小时':
  4629. ut = round(ut / 60.0, 2)
  4630. else:
  4631. ut = round(ut / (60.0 * 24.0), 2)
  4632. leftCount = count - ut
  4633. elif unit in [u'度']:
  4634. leftCount = count - ue
  4635. elif unit in [u'次']: # 如果没有用,比如没有插电,一样,不能扣除配额
  4636. if int(ut) == 0 and int(ue) == 0:
  4637. leftCount = count
  4638. else:
  4639. leftCount = 0
  4640. elif unit in [u'币']: # 注意,这个是已经算好的退币数额
  4641. leftCount = float(VirtualCoin(uc))
  4642. else:
  4643. leftCount = 0
  4644. return leftCount
  4645. # 当消费已经定下来的时候 则本次退换的单位实际上已经固定了
  4646. consumeLeftTemp = calc_left_by_unit(
  4647. consumeRcd.consumeData['count'],
  4648. consumeRcd.consumeData['unit'],
  4649. usedTime,
  4650. spendElec,
  4651. backCoins
  4652. )
  4653. consumeLeft = max(consumeLeftTemp,0)
  4654. consumeRcd.consumeData['count'] -= float(consumeLeft)
  4655. consumeDayLeft = calc_left_by_unit(
  4656. consumeRcd.consumeDayData['count'],
  4657. consumeRcd.consumeDayData['unit'],
  4658. usedTime,
  4659. spendElec,
  4660. backCoins
  4661. )
  4662. consumeRcd.consumeDayData['count'] -= float(consumeDayLeft)
  4663. # 刷新消费记录数据
  4664. try:
  4665. consumeRcd.save()
  4666. except Exception as e:
  4667. logger.exception('[refund_quota] save consume rcd error = %s' % e)
  4668. return
  4669. # 更新额度
  4670. for tDict in self.quotaUsed:
  4671. if tDict['unit'] != consumeRcd.consumeData['unit']:
  4672. continue
  4673. tDict['count'] -= consumeLeft
  4674. dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
  4675. for tDict in self.dayUsed.get(dayKey, []):
  4676. if tDict['unit'] != consumeRcd.consumeDayData['unit']:
  4677. continue
  4678. tDict['count'] -= float(consumeDayLeft)
  4679. try:
  4680. self.save()
  4681. except Exception as e:
  4682. logger.exception('[refund_quota] save quota error=%s' % e)
  4683. # 返回本条记录余下的配套
  4684. returnUsedTime,returnSpendElec,returnBackCoins = usedTime,spendElec,backCoins
  4685. if consumeRcd.consumeData['unit'] in [u'分钟',u'小时',u'秒']:
  4686. return consumeLeftTemp,returnSpendElec,returnBackCoins
  4687. if consumeRcd.consumeData['unit'] in [u'度']:
  4688. return returnUsedTime,consumeLeftTemp,returnBackCoins
  4689. return returnUsedTime,returnSpendElec,returnBackCoins
  4690. # 卡续费
  4691. def continue_card(self):
  4692. if not self.continueTime:
  4693. return
  4694. vCard = VirtualCard.objects.get(id = self.cardTypeId)
  4695. # 以最新的属性作为 购买
  4696. self.price = vCard.price
  4697. self.dayQuota = vCard.dayQuota
  4698. self.userLimit = vCard.userLimit
  4699. self.cardName = vCard.cardName
  4700. self.quota = vCard.quota
  4701. self.userDesc = vCard.userDesc
  4702. self.dayUsed = {}
  4703. self.quotaUsed = []
  4704. self.status = 'normal'
  4705. self.expiredTime = self.continueTime + datetime.timedelta(seconds=vCard.periodDays * 24 * 3600)
  4706. self.continueTime = None
  4707. self.save()
  4708. return self
  4709. @staticmethod
  4710. def get_user_cards(openId, groupId, ownerId):
  4711. """
  4712. 获取用户所有的虚拟卡
  4713. 修改: 去掉continueTime的check判断 没意义
  4714. """
  4715. cardList = []
  4716. vCards = UserVirtualCard.objects.filter(
  4717. dealerId = ownerId, openIds = openId,
  4718. groupIds__in = ['*', groupId]
  4719. )
  4720. nowTime = datetime.datetime.now()
  4721. for obj in vCards:
  4722. if obj.startTime <= nowTime <= obj.expiredTime:
  4723. cardList.append(obj)
  4724. return cardList
  4725. def add_expired_time(self, days):
  4726. self.expiredTime += datetime.timedelta(days = int(days))
  4727. self.save()
  4728. def support_dev_type(self, devTypeCode):
  4729. devTypesCount = DeviceType.objects.filter(code = devTypeCode, id__in = self.devTypeList).count()
  4730. if devTypesCount <= 0:
  4731. return False
  4732. return True
  4733. @property
  4734. def groupInfo(self):
  4735. """
  4736. 获取虚拟卡的相应的 地址信息
  4737. :return:
  4738. """
  4739. if '*' in self.groupIds:
  4740. groups = [{'groupName': grp['groupName'], 'address': grp['address']} for grp in
  4741. Group.get_groups_by_group_ids(Group.get_group_ids_of_dealer(self.dealerId)).values()]
  4742. else:
  4743. groups = [{'groupName': grp['groupName'], 'address': grp['address']} for grp in
  4744. Group.get_groups_by_group_ids(self.groupIds).values()]
  4745. return groups
  4746. @property
  4747. def membersInfo(self):
  4748. """
  4749. 虚拟卡成员信息
  4750. :return:
  4751. """
  4752. users = MyUser.objects.filter(openId__in = self.openIds).only("openId", "nickname")
  4753. bindUsersList = list()
  4754. repeatUsers = set()
  4755. for user in users:
  4756. if user.openId in repeatUsers:
  4757. continue
  4758. # openId 相同去重
  4759. repeatUsers.add(user.openId)
  4760. tempDict = {
  4761. 'userName': user.nickname,
  4762. 'userId': user.openId
  4763. }
  4764. user.openId == self.ownerOpenId and tempDict.update({"role": "owner"})
  4765. bindUsersList.append(tempDict)
  4766. return bindUsersList
  4767. @property
  4768. def quotaInfo(self):
  4769. """
  4770. 获取和额度相关的信息
  4771. :return:
  4772. """
  4773. dayUsedList = self.dayUsed.get(datetime.datetime.now().strftime("%Y-%m-%d"), list())
  4774. dayUsed = 0
  4775. if len(dayUsedList) > 0:
  4776. dayUsed = dayUsedList[0]['count']
  4777. return {
  4778. 'quota': self.quota[0]['count'],
  4779. 'quotaUnit': self.quota[0]['unit'],
  4780. 'quotaUsed': round(self.quotaUsed[0]['count'], 2) if len(self.quotaUsed) > 0 else 0,
  4781. 'dayQuota': self.dayQuota[0]['count'],
  4782. 'dayQuotaUnit': self.dayQuota[0]['unit'],
  4783. 'dayUsed': round(dayUsed, 2)
  4784. }
  4785. @property
  4786. def devTypes(self):
  4787. devs = DeviceType.objects.filter(id__in=self.devTypeList).only("majorDeviceType", "name")
  4788. devTypes = list()
  4789. for _dev in devs: # type: DeviceType
  4790. devTypes.append({"devTypeName": _dev.name, "majorDeviceType": _dev.majorDeviceType})
  4791. return devTypes
  4792. def isOwner(self, openId):
  4793. """是不是卡的所有者"""
  4794. return openId == self.ownerOpenId
  4795. def to_dict(self):
  4796. """
  4797. 序列化
  4798. :return:
  4799. """
  4800. data = {
  4801. 'cardId': str(self.id),
  4802. 'cardNo': self.cardNo,
  4803. 'cardTypeId': self.cardTypeId,
  4804. 'cardName': self.cardName,
  4805. 'periodDays': self.periodDays,
  4806. 'createTime': self.startTime.strftime('%Y-%m-%d'),
  4807. 'expiredTime': self.expiredTime.strftime('%Y-%m-%d'),
  4808. 'userDesc': self.userDesc,
  4809. 'userLimit': self.userLimit,
  4810. 'userName': self.nickname,
  4811. 'logicalCode': self.logicalCode,
  4812. 'groupId': self.groupId,
  4813. 'remark': self.remark,
  4814. 'power': self.power
  4815. }
  4816. return data
  4817. def to_detail(self):
  4818. data = {
  4819. 'cardId': str(self.id),
  4820. 'cardNo': self.cardNo,
  4821. 'cardTypeId': self.cardTypeId,
  4822. 'cardName': self.cardName,
  4823. 'periodDays': self.periodDays,
  4824. 'createTime': self.startTime.strftime('%Y-%m-%d'),
  4825. 'expiredTime': self.expiredTime.strftime('%Y-%m-%d'),
  4826. 'userDesc': self.userDesc,
  4827. 'userLimit': self.userLimit,
  4828. 'userName': self.nickname,
  4829. 'logicalCode': self.logicalCode,
  4830. 'groupId': self.groupId,
  4831. 'remark': self.remark,
  4832. "groups": self.groupInfo,
  4833. "sharedMembers": self.membersInfo,
  4834. "devTypes": self.devTypes,
  4835. # 功率
  4836. 'power': self.power,
  4837. # 额度
  4838. 'quota': {
  4839. "day": self.dayQuota,
  4840. "total": self.quota
  4841. },
  4842. # 剩余的额度
  4843. 'leftQuota': {
  4844. "day": self.calc_left_day_quota(today_format_str()),
  4845. "total": self.calc_left_total_quota()
  4846. }
  4847. }
  4848. return data
  4849. # 以下是霍坡的特殊业务 之前是不支持按次 现在支持了 考虑业务合并
  4850. def can_use_hp_gate(self):
  4851. """霍珀虚拟卡 对于道闸计费 只要卡没过期就可以使用"""
  4852. # 尚未过期的虚拟卡可用于霍珀道闸
  4853. # 要求变更 虚拟卡过期后还能在使用24小时
  4854. if self.status not in ["normal", "warning"]:
  4855. return False
  4856. return self.expiredTime > datetime.datetime.now() - datetime.timedelta(hours = 24)
  4857. def consume_hp_gate(self, openId, group, dev, package = None, attachParas={}, nickname=''):
  4858. # type:(str, GroupDict, DeviceDict, dict, dict, str)->Optional[VCardConsumeRecord]
  4859. """
  4860. 消费一次
  4861. :param openId:
  4862. :param group:
  4863. :param dev:
  4864. :param package:
  4865. :param attachParas:
  4866. :param nickname:
  4867. :return:
  4868. """
  4869. # 增加一条消费记录
  4870. newRcd = VCardConsumeRecord(
  4871. orderNo = VCardConsumeRecord.make_no(dev.logicalCode),
  4872. openId = openId,
  4873. nickname = nickname,
  4874. cardId = str(self.id),
  4875. dealerId = self.dealerId,
  4876. devNo = dev['devNo'],
  4877. devTypeCode = dev.devTypeCode,
  4878. devTypeName = dev.devTypeName,
  4879. logicalCode = dev['logicalCode'],
  4880. groupId = group['groupId'],
  4881. address = group['address'],
  4882. groupNumber = dev['groupNumber'],
  4883. groupName = group['groupName'],
  4884. attachParas = attachParas,
  4885. remarks = u"道闸刷卡服务"
  4886. )
  4887. try:
  4888. newRcd.save()
  4889. self.record_gate_used_times()
  4890. except Exception as e:
  4891. logger.error('save vcard consume e=%s' % e)
  4892. return None
  4893. return newRcd
  4894. def record_gate_used_times(self):
  4895. """
  4896. 霍珀要求的 对于道闸次数进行监控,但是不能限制次数
  4897. :return:
  4898. """
  4899. dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
  4900. cacheKey = "{}-{}".format(dayKey, self.cardNo)
  4901. times = serviceCache.get(cacheKey, 0)
  4902. times += 1
  4903. serviceCache.set(cacheKey, times, timeout = 60 * 60 * 24)
  4904. if times < 8:
  4905. status = "normal"
  4906. else:
  4907. status = "warning"
  4908. try:
  4909. self.update(status = status)
  4910. except Exception as e:
  4911. logger.exception(e)
  4912. return
  4913. class VirtualCardRechargeRecord(Searchable):
  4914. orderNo = StringField(verbose_name = u'使用RechargeRecord的orderNo关联两张表', required = True)
  4915. cardId = StringField(verbose_name = "虚拟卡ID", default = "")
  4916. cardNo = StringField(verbose_name = "虚拟卡卡号", default = "")
  4917. openId = StringField(verbose_name = "openId", default = "")
  4918. gateway = StringField(verbose_name = 'gateway', default = "")
  4919. groupId = StringField(verbose_name = "设备地址编号", default = "")
  4920. ownerId = ObjectIdField(verbose_name = "dealerId")
  4921. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '生成时间')
  4922. # 以下字段后续废弃, 只做兼容使用
  4923. devNo = StringField(verbose_name = "设备ID", default = None)
  4924. logicalCode = StringField(verbose_name = "设备逻辑编码", default = None)
  4925. wxOrderNo = StringField(verbose_name = "orderNo", default = None)
  4926. money = MonetaryField(verbose_name = "付款金额", default = None)
  4927. coins = MonetaryField(verbose_name = "充值金额", default = None)
  4928. devTypeCode = StringField(verbose_name = "设备类型编码", default = None)
  4929. time = StringField(verbose_name = 'time', default = None)
  4930. devType = StringField(verbose_name = "设备类型", default = None)
  4931. address = StringField(verbose_name = "设备地址", default = None)
  4932. groupNumber = StringField(verbose_name = "设备", default = None)
  4933. groupName = StringField(verbose_name = "交易场地", default = None)
  4934. remarks = StringField(verbose_name = "备注", default = None)
  4935. status = StringField(verbose_name = "状态", default = None)
  4936. meta = {
  4937. "collection": "virtualCardRechargeRecord",
  4938. "db_alias": "default",
  4939. }
  4940. @classmethod
  4941. def get_link_orderNo_list(cls, cardId, startTime, endTime, openId = None):
  4942. records = cls.objects(
  4943. cardId = cardId, dateTimeAdded__gte = startTime, dateTimeAdded__lt = get_tomorrow_zero_time(endTime))
  4944. if openId:
  4945. records = records.filter(openId = openId)
  4946. return [record.orderNo for record in records]
  4947. @property
  4948. def created_date(self):
  4949. return self.dateTimeAdded
  4950. def is_refund_available(self, customer): # type:(CapitalUser) -> bool
  4951. return False
  4952. @property
  4953. def has_refund_order(self):
  4954. return False
  4955. @property
  4956. def isQuickPay(self):
  4957. return False
  4958. @classmethod
  4959. def issue(cls, order):
  4960. # type:(RechargeRecord)->VirtualCardRechargeRecord
  4961. return cls(
  4962. orderNo = order.orderNo,
  4963. cardId = order.attachParas['cardId'],
  4964. cardNo = order.attachParas['cardNo'],
  4965. openId = order.openId,
  4966. ownerId = order.ownerId,
  4967. groupId = order.groupId,
  4968. gateway = order.gateway,
  4969. dateTimeAdded = order.dateTimeAdded).save()
  4970. # 监督号的用户,用于和实际系统的用户进行关联
  4971. class MoniUser(Searchable):
  4972. moniAppId = StringField(verbose_name = 'moniAppId', default = '')
  4973. moniOpenId = StringField(verbose_name = 'moniOpenId', default = '')
  4974. openId = StringField(verbose_name = u'用户的主APPID', default = '')
  4975. isSubscribe = BooleanField(verbose_name = u'是否订阅', default = False)
  4976. checkTime = DateTimeField(verbose_name = u'检查时间')
  4977. subTime = DateTimeField(verbose_name = u'关注时间')
  4978. unsubTime = DateTimeField(verbose_name = u'取关时间')
  4979. subLogicalCode = StringField(verbose_name = u'关注来源logicalCode', default = '')
  4980. subDealerId = StringField(verbose_name = u'关注来源dealerId', default = '')
  4981. subAgentId = StringField(verbose_name = u'关注来源agentId', default = '')
  4982. subPoint = StringField(verbose_name = u'关注来源point', default = '')
  4983. meta = {
  4984. "collection": "moni_user",
  4985. "db_alias": "logdata",
  4986. }
  4987. @classmethod
  4988. def get_or_create(cls, moniOpenId, moniAppId, **kwargs):
  4989. """
  4990. 创建一条用户的记录
  4991. """
  4992. obj = cls.objects.filter(moniOpenId=moniOpenId, moniAppId=moniAppId).order_by("-id").first() or cls(moniOpenId=moniOpenId, moniAppId=moniAppId)
  4993. needUpdate = False
  4994. for _field in cls._fields.keys():
  4995. if _field in kwargs and kwargs[_field] != getattr(obj, _field, kwargs[_field]):
  4996. setattr(obj, _field, kwargs[_field])
  4997. needUpdate = True
  4998. if needUpdate:
  4999. obj = obj.save()
  5000. return obj
  5001. @classmethod
  5002. def get_or_delete(cls, moniOpenId, moniAppId):
  5003. cls.objects.filter(moniOpenId=moniOpenId, moniAppId=moniAppId).update(isSubscribe=False)
  5004. class AskStep(Searchable):
  5005. operKey = StringField(verbose_name = 'oper key', default = '')
  5006. answerText = StringField(verbose_name = 'answer text', default = '')
  5007. answerPics = StringField(verbose_name = 'answer picture', default = '') # '文件名称用,分割'
  5008. answerFunc = StringField(verbose_name = 'answer func', default = '')
  5009. @staticmethod
  5010. def replyUseDevice(moniOpenId, appId, **kwargs):
  5011. moniUser = MoniUser.objects.filter(moniOpenId = moniOpenId, moniAppId = appId).first()
  5012. if moniUser:
  5013. devLink = concat_user_login_entry_url(l=moniUser.subLogicalCode)
  5014. return [{'msgType': 'text', 'value': u'\ue231<a href="%s">戳我使用设备</a>\ue230' % devLink}]
  5015. return [{'msgType': 'text', 'value': u'您直接点击菜单栏的扫一扫,或者关闭当前页面重新扫码,即可使用'}]
  5016. @staticmethod
  5017. def replayQueryBalance(moniOpenId, appId, **kwargs):
  5018. openId = kwargs.get('openId')
  5019. agentId = kwargs.get('agentId')
  5020. users = MyUser.objects.filter(openId = openId)
  5021. balance = VirtualCoin(0.0)
  5022. for user in users:
  5023. balance += user.balance
  5024. url = concat_user_center_entry_url(agentId=agentId,
  5025. redirect=concat_front_end_url(uri='/user/index.html#/user/help'))
  5026. return [{'msgType': 'text',
  5027. 'value': u'您在系统总的余额是%s,如果界面上显示不对,请您先刷新页面,还是不行的话,<a href="%s">联系客服</a>' % (balance, url)}]
  5028. @staticmethod
  5029. def get_service_phone_from_rcd(consumeRcd):
  5030. dealer = Dealer.objects(id=consumeRcd.ownerId).first() # type: Dealer
  5031. return dealer.service_phone
  5032. @staticmethod
  5033. def replyGetToushuUrl(moniOpenId, appId, **kwargs):
  5034. agentId = kwargs.get('agentId')
  5035. url = concat_user_center_entry_url(agentId=agentId,
  5036. redirect=concat_front_end_url(uri='/user/index.html#/complaint/list'))
  5037. return [{'msgType': 'text', 'value': u'<a href="%s">投诉点这里</a>' % url}]
  5038. # 先检查用户的订单是否扣掉了钱。然后检查设备的连通性。
  5039. # 调用此函数的时候,一般是用户付款了,设备没有响应。
  5040. @staticmethod
  5041. def replyQueryConsumeRcd(moniOpenId, appId, **kwargs):
  5042. try:
  5043. endTime = datetime.datetime.now()
  5044. startTime = endTime - datetime.timedelta(days = 7)
  5045. consumeRcd = ConsumeRecord.objects.filter(openId = kwargs['openId'], dateTimeAdded__gte = startTime,
  5046. dateTimeAdded__lte = endTime).order_by('-dateTimeAdded').first()
  5047. agentId = kwargs.get('agentId')
  5048. url = concat_user_center_entry_url(agentId=agentId,
  5049. redirect=concat_front_end_url(uri='/user/index.html#/user/feedbackList'))
  5050. if consumeRcd is None:
  5051. return [
  5052. {'msgType': 'text',
  5053. 'value': u'系统中没有查到您最近7天的使用记录/::-|,这种情况比较少见,建议您先创建一张<a href="%s">报告单</a>,然后拨打客服电话,让客服尽快处理' % url},
  5054. {'msgType': 'image', 'value': '1.png'},
  5055. {'msgType': 'image', 'value': '2.png'},
  5056. {'msgType': 'image', 'value': '3.png'},
  5057. {'msgType': 'image', 'value': '4.png'},
  5058. ]
  5059. rcdDesc = u'地址:%s,编号:%s,付款:%s,时间:%s' % (consumeRcd.address, consumeRcd.logicalCode, consumeRcd.coin,
  5060. consumeRcd.dateTimeAdded.strftime(Const.DATETIME_FMT))
  5061. if consumeRcd.isNormal:
  5062. return [
  5063. {'msgType': 'text', 'value': u'查到您最新的订单,信息如下:%s' % rcdDesc},
  5064. {'msgType': 'text',
  5065. 'value': u'钱确实用出去了/:--b,设备如果没有正常响应或者故障,您先拍照留下证据。然后您在服务&投诉中,<a href="%s">报告老板</a>,申请上分或者退币。然后在常见问题菜单中,拨打客服电话,让客服尽快处理/::)。如果没有及时响应,您也可以先行自己补款,然后记得向设备老板申请退币/:rose' % url}
  5066. ]
  5067. else:
  5068. return [
  5069. {'msgType': 'text', 'value': u'查到您最新的订单,信息如下:%s' % rcdDesc},
  5070. {'msgType': 'text', 'value': u'亲,刚查询到您确实有个消费订单,没有成功/:--b。系统已经自动给您退币了,您可以重新扫码尝试启动设备,或者换台别的设备试试吧/::)'}
  5071. ]
  5072. except Exception, e:
  5073. return [{'msgType': 'text',
  5074. 'value': u'我在系统中没有查到您最近的使用记录/::<,这种情况,建议您在服务&投诉中<a href="%s">报告老板</a>,申请上分或者退币。然后在常见问题中,拨打客服电话,让客服尽快处理/::P' % url}]
  5075. @staticmethod
  5076. def replyQueryServicedPhone(moniOpenId, appId, **kwargs):
  5077. agentId = kwargs.get('agentId')
  5078. endTime = datetime.datetime.now()
  5079. startTime = endTime - datetime.timedelta(days = 7)
  5080. consumeRcd = ConsumeRecord.objects.filter(openId = kwargs['openId'], dateTimeAdded__gte = startTime,
  5081. dateTimeAdded__lte = endTime).order_by('-dateTimeAdded').first()
  5082. if consumeRcd:
  5083. phone = AskStep.get_service_phone_from_rcd(consumeRcd)
  5084. return [{'msgType': 'text', 'value': u'/::P这个是客服电话吧:%s' % phone}]
  5085. else:
  5086. url = concat_user_center_entry_url(agentId=agentId,
  5087. redirect=concat_front_end_url(uri='/user/index.html#/user/help'))
  5088. return [{'msgType': 'text', 'value': u'这个给您,<a href="%s">客服电话</a>' % url}]
  5089. def reply(self, moniOpenId, appId, **kwargs):
  5090. if self.answerFunc:
  5091. return eval('AskStep.%s(moniOpenId,appId,**kwargs)' % self.answerFunc)
  5092. elif self.answerText:
  5093. return [{'msgType': 'text', 'value': self.answerText}]
  5094. elif self.answerPics:
  5095. return [{'msgType': 'image', 'value': pic} for pic in self.answerPics]
  5096. else:
  5097. return [{'msgType': 'text', 'value': ''}]
  5098. # 聊天模式匹配库。用于批量智能客服(不需要那么复杂的聊天,而且也贵;可以针对性进行数据库查询,更灵活)
  5099. class AskRobot(Searchable):
  5100. ask = StringField(verbose_name = 'ask', default = '') # 问题模式,是可以匹配的: 机器/洗衣机/设备/,不启动/没反应/不动/不转/没动静
  5101. steps = ListField(verbose_name = 'answer steps', default = []) # 支持
  5102. priority = IntField(verbose_name = 'priority', default = 1)
  5103. askList = []
  5104. @staticmethod
  5105. def load_in_mem_if_not_exist():
  5106. # if AskRobot.askList:#调试比较麻烦,直接查询所有吧,以后做了界面,再打开
  5107. # return
  5108. objs = AskRobot.objects.all().order_by('-priority')
  5109. for obj in objs:
  5110. AskRobot.askList.append(obj)
  5111. @staticmethod
  5112. def match_sentence(ask, sentence, score):
  5113. wordList = sentence.split('/')
  5114. for word in wordList:
  5115. if word in ask:
  5116. logger.info('it is match,sentence=%s,ask=%s' % (sentence, ask))
  5117. return score
  5118. return 0.0
  5119. # 根据图片文件名称找ID,文章标题找ID
  5120. @staticmethod
  5121. def find_material_id(proxy, mediaType, value):
  5122. if mediaType == 'image':
  5123. results = proxy.client.material.batchget(media_type = 'image', offset = 0,
  5124. count = 1000) # 简单粗暴,目前看素材不会超过20个
  5125. for item in results.get('item', []):
  5126. if item['name'] == value:
  5127. return item['media_id']
  5128. return None
  5129. return None
  5130. @staticmethod
  5131. def reply(event, moniOpenId, ask, appId, secret, **kwargs):
  5132. AskRobot.load_in_mem_if_not_exist()
  5133. # 产生一个线程,让线程后台处理这个客服消息
  5134. class replyer(threading.Thread):
  5135. def __init__(self, event, moniOpenId, ask, appId, secret, **kwargs):
  5136. super(replyer, self).__init__()
  5137. self._event = event
  5138. self._moniOpenId = moniOpenId
  5139. self._ask = ask
  5140. self._appId = appId
  5141. self._secret = secret
  5142. self._kwargs = kwargs
  5143. def run(self):
  5144. try:
  5145. replys = []
  5146. # 如果是关注事件,直接推送文章
  5147. if self._event == 'subscribe':
  5148. materialId = MoniApp.objects.get(appId = self._appId).welcomeArticleMaterialId
  5149. replys.append({'msgType': 'news', 'value': materialId})
  5150. moniUser = MoniUser.objects.filter(moniOpenId = self._moniOpenId,
  5151. moniAppId = self._appId).first()
  5152. if moniUser:
  5153. devLink = concat_user_login_entry_url(l=moniUser.subLogicalCode)
  5154. replys.append({'msgType': 'text',
  5155. 'value': u'/:rose亲,欢迎您关注自助设备服务平台!乐乐可以为您解决售后问题以及投诉事宜/:hug,——————— \ue231<a href="%s">戳我使用设备</a>\ue230' % devLink})
  5156. else:
  5157. # 找到所有问题的答案
  5158. for askConfig in AskRobot.askList:
  5159. sentenceList = askConfig.ask.split(',')
  5160. sentenceScore = 100.0 / len(sentenceList)
  5161. allScore = 0
  5162. for sentence in sentenceList:
  5163. allScore += AskRobot.match_sentence(self._ask, sentence, sentenceScore)
  5164. if allScore >= 90:
  5165. logger.info('find match answer key = %s' % ','.join(askConfig.steps))
  5166. replys = askConfig.answer(self._moniOpenId, self._appId, **self._kwargs)
  5167. break
  5168. logger.info('reply len %s' % len(replys))
  5169. # 把答案的文章、图片发给用户
  5170. app = WechatAuthApp(appid = self._appId, secret = self._secret)
  5171. proxy = WechatClientProxy(app)
  5172. for reply in replys:
  5173. if not reply:
  5174. continue
  5175. try:
  5176. if reply['msgType'] == 'text' and reply['value']:
  5177. proxy.client.message.send_text(moniOpenId, reply['value'])
  5178. elif reply['msgType'] == 'image' and reply['value']:
  5179. materialId = AskRobot.find_material_id(proxy, 'image', reply['value'])
  5180. proxy.client.message.send_image(moniOpenId, materialId)
  5181. elif reply['msgType'] == 'news' and reply['value']:
  5182. proxy.client.message.send_articles(moniOpenId, reply['value'])
  5183. time.sleep(1)
  5184. except Exception, e:
  5185. logger.exception('send msg to user failed = %s' % (e))
  5186. continue
  5187. except Exception as e:
  5188. logger.info('replyer error,e=%s' % e)
  5189. sender = replyer(event, moniOpenId, ask, appId, secret, **kwargs)
  5190. sender.start()
  5191. def answer(self, moniOpenId, appId, **kwargs):
  5192. # 首先把当前回话的历史信息拉出来
  5193. historyReplys = serviceCache.get(moniOpenId, '')
  5194. replyList = []
  5195. for stepKey in self.steps:
  5196. step = AskStep.objects.get(operKey = stepKey)
  5197. sameReplyCount = historyReplys.count(stepKey)
  5198. if sameReplyCount == 0:
  5199. reply = step.reply(moniOpenId, appId, **kwargs)
  5200. elif sameReplyCount == 1:
  5201. if 'xiangyin' not in stepKey:
  5202. reply = [{'msgType': 'text', 'value': u'建议您试试我给的建议哦/::P'}].extend(
  5203. step.reply(moniOpenId, appId, **kwargs))
  5204. else:
  5205. reply = [{'msgType': 'text', 'value': u'嗯,您的问题是什么呢?/::P'}]
  5206. elif sameReplyCount == 2:
  5207. step = AskStep.objects.get(operKey = 'kefudianhua')
  5208. reply = step.reply(moniOpenId, appId, **kwargs)
  5209. else:
  5210. reply = [{'msgType': 'text', 'value': ''}]
  5211. if reply:
  5212. historyReplys += '%s,' % stepKey
  5213. serviceCache.set(moniOpenId, historyReplys, 600)
  5214. replyList.extend(reply)
  5215. return replyList
  5216. class WxAuthTransfer(Searchable):
  5217. agentId = StringField(verbose_name = u'代理商ID', default = '')
  5218. oldAppId = StringField(verbose_name = u'老的appid', default = '')
  5219. oldOpenId = StringField(verbose_name = u'老的openid', default = '')
  5220. newAppId = StringField(verbose_name = u'新的appid', default = '')
  5221. newOpenId = StringField(verbose_name = u'新的openid', default = '')
  5222. # subAgentList = ListField(verbose_name = '子agent列表', default = '')
  5223. firstPhase = BooleanField(verbose_name = u'第1阶段任务完成', default = False)
  5224. secondPhase = BooleanField(verbose_name = u'第2阶段任务完成', default = False)
  5225. meta = {
  5226. "collection": "wx_auth_transfer",
  5227. "db_alias": "logdata"
  5228. }
  5229. def __repr__(self):
  5230. return '{}<agent={}, old=({},{}), new=({},{})>'.format(
  5231. self.__class__.__name__,
  5232. str(self.agentId),
  5233. self.oldAppId, self.oldOpenId,
  5234. self.newAppId, self.newOpenId)
  5235. class ICChargeOperating(Searchable):
  5236. """
  5237. deprecated 该功能废弃, 保证脚本能正常进行
  5238. """
  5239. cardId = StringField(verbose_name = 'cardId', default = '')
  5240. balance = MonetaryField(verbose_name = u"卡余额", default = RMB('0.00'))
  5241. needChargeMoney = MonetaryField(verbose_name = u"需要充值的钱", default = RMB('0.00'))
  5242. dateTimeAdded = DateTimeField(verbose_name = u'操作时间', default = datetime.datetime.now)
  5243. meta = {
  5244. "collection": "ic_charge_operating",
  5245. "db_alias": "logdata"
  5246. }
  5247. class BlackListUsers(Searchable):
  5248. class Status(IterConstant):
  5249. BLACK = "black"
  5250. WHITE = 'white'
  5251. openId = StringField(verbose_name = "被拉黑用户OpenId")
  5252. dealerId = StringField(verbose_name = "经销商ID")
  5253. agentId = StringField(verbose_name = "代理商ID")
  5254. reason = StringField(verbose_name = "被拉黑的理由", default = "")
  5255. operator = StringField(verbose_name = "操作人")
  5256. status = StringField(verbose_name = "拉黑状态", choices = Status.choices(), default = Status.BLACK)
  5257. dateTimeAdded = DateTimeField(verbose_name = "拉黑进去的时间", default = datetime.datetime.now)
  5258. meta = {
  5259. "db_alias": "default",
  5260. 'indexes': [
  5261. 'dealerId',
  5262. ],
  5263. }
  5264. @classmethod
  5265. def add_black_user(cls, openId, dealerId, operator, reason = ""):
  5266. """
  5267. 拉黑用户
  5268. :param openId:
  5269. :param dealerId:
  5270. :param operator:
  5271. :param reason:
  5272. :return:
  5273. """
  5274. try:
  5275. record = cls.objects.get(dealerId = dealerId, openId = openId)
  5276. except DoesNotExist:
  5277. dealer = Dealer.get_dealer(dealerId) or dict()
  5278. record = cls(
  5279. openId = openId,
  5280. dealerId = dealerId,
  5281. agentId = dealer.get("agentId", ""),
  5282. operator = operator,
  5283. reason = reason
  5284. ).save()
  5285. record.update(status = cls.Status.BLACK)
  5286. @classmethod
  5287. def freed_black_user(cls, openId, dealerId):
  5288. """
  5289. 解除用户的拉黑状态 这个地方不删除记录 防止之后要有需求对拉黑用户进行天数限制
  5290. :param openId:
  5291. :param dealerId:
  5292. :return:
  5293. """
  5294. try:
  5295. record = cls.objects.get(dealerId = dealerId, openId = openId)
  5296. except DoesNotExist:
  5297. return
  5298. record.update(status = cls.Status.WHITE)
  5299. @classmethod
  5300. def check_black(cls, openId, dealerId):
  5301. """
  5302. 检查该用户是否被拉黑
  5303. :param openId:
  5304. :param dealerId:
  5305. :return:
  5306. """
  5307. record = cls.objects(openId=openId, dealerId__in=[dealerId, '*']).first()
  5308. if not record:
  5309. return False
  5310. if record.status == cls.Status.WHITE:
  5311. return False
  5312. else:
  5313. return True
  5314. # 此表用于解决复制卡的问题。每张卡都有一个变化的随机码,每次扣费后会新生成一个随机码。每次扣钱后,会上报上次的随机码
  5315. # 如果随机码出现一次重复,说明出现一次复制卡。随机码是刷卡器生成的时间标签,恰好重复的可能性微乎其微。
  5316. class WeifuleCardStamp(Searchable):
  5317. cardNo = StringField(verbose_name = "卡号", default = "")
  5318. dealerId = StringField(verbose_name = "经销商ID", default = "")
  5319. dateTimeAdded = DateTimeField(verbose_name = '添加时间', default = datetime.datetime.now())
  5320. stamp = StringField(verbose_name = 'stamp', default = "")
  5321. meta = {
  5322. "collection": "weifule_card_stamp",
  5323. "db_alias": "default"
  5324. }
  5325. @staticmethod
  5326. def is_copy_card(cardNo, dealerId, stamp):
  5327. harfYear = datetime.datetime.now() - datetime.timedelta(days = 6 * 30)
  5328. copyCount = WeifuleCardStamp.objects.filter(cardNo = cardNo, dealerId = dealerId, dateTimeAdded__gte = harfYear,
  5329. stamp = stamp).count()
  5330. if copyCount:
  5331. return True
  5332. return False
  5333. class SwapCardRecord(Searchable):
  5334. oldCardNo = StringField(verbose_name = u"旧卡卡号")
  5335. newCardNo = StringField(verbose_name = u"新卡卡号")
  5336. agentId = StringField(verbose_name = u"补卡的代理商ID")
  5337. operator = StringField(verbose_name = u"操作人")
  5338. dateTimeAdded = DateTimeField(verbose_name = u"补卡时间", default = datetime.datetime.now)
  5339. meta = {'collection': 'SwapCardRecord', 'db_alias': 'logdata'}
  5340. @classmethod
  5341. def add_record(cls, oldCardNo, newCardNo, agentId, operator):
  5342. """
  5343. 添加一条补卡记录
  5344. :param oldCardNo: 旧卡卡号
  5345. :param newCardNo: 新卡卡号
  5346. :param agentId: 补卡代理商ID
  5347. :param operator: 操作者ID
  5348. :return:
  5349. """
  5350. record = cls(
  5351. newCardNo = newCardNo,
  5352. oldCardNo = oldCardNo,
  5353. agentId = agentId,
  5354. operator = operator
  5355. )
  5356. record.save()
  5357. return record
  5358. class DuibijiOrderMap(Searchable):
  5359. devOrderId = IntField(verbose_name = 'device order id', unique = True)
  5360. consumeRcdId = StringField(verbose_name = 'consume rcd id', unique = True)
  5361. rechargeRcdId = StringField(verbose_name = 'rechargeRcdId id', default = '')
  5362. status = StringField(verbose_name = 'status', default = '')
  5363. openId = StringField(verbose_name = 'openId', default = '')
  5364. dateTimeAdded = DateTimeField(verbose_name = 'dateTimeAdded', default = datetime.datetime.now())
  5365. class MonthlyPackageUseInfo(EmbeddedDocument):
  5366. orderNo = StringField(verbose_name = u"使用订单编号", required = True)
  5367. orderTime = DateTimeField(verbose_name = u"使用的时间", required = True)
  5368. def to_dict(self):
  5369. return {"orderNo": self.orderNo, "orderTime": self.orderTime}
  5370. class MonthlyPackage(Searchable):
  5371. # 用户相关信息
  5372. name = StringField(verbose_name = u"包月套餐的名称", default = "")
  5373. groupId = StringField(verbose_name = u"包月绑定的地址组", required = True)
  5374. openId = StringField(verbose_name = u"用户的唯一ID", required = True)
  5375. cardNo = StringField(verbose_name = u"刷卡能够使用的卡号", default = "")
  5376. bothCardAndMobile = BooleanIntField(verbose_name = u"是否是通用的(扫码和刷卡通用)", default = False)
  5377. # 包月相关信息
  5378. startDateTime = DateTimeField(verbose_name = u"有效期起始时间", required = True)
  5379. expireDateTime = DateTimeField(verbose_name = u"过期的时间", required = True)
  5380. maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 0, min_value = 0)
  5381. maxCount = IntField(verbose_name = u"可供使用的最大次数", default = 0, min_value = 0)
  5382. maxTimeOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 0, min_value = 0)
  5383. maxElecOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 0, min_value = 0)
  5384. # 基本信息
  5385. isDisable = BooleanIntField(verbose_name = u"是否已经失效", default = 0)
  5386. usedTotal = IntField(verbose_name = u"使用的总次数", default = 0)
  5387. usedDetail = MapField(verbose_name = u"用户使用情况",
  5388. field = EmbeddedDocumentListField(document_type = MonthlyPackageUseInfo))
  5389. dateTimeAdded = DateTimeField(verbose_name = u"购买的时间 一般与起始时间开始", default = datetime.datetime.now)
  5390. @property
  5391. def payVia(self):
  5392. return "monthlyPackage"
  5393. @staticmethod
  5394. def format_date(date): # type: (datetime.date) -> str
  5395. """
  5396. :param date:
  5397. :return:
  5398. """
  5399. return "T{}".format(date.strftime("%Y_%m_%d"))
  5400. @classmethod
  5401. def get_enable_one(cls, openId, groupId, cardNo = ""):
  5402. """
  5403. 找包月的 openId groupId 一定需要对应上 并且 允许通用 或者不允许通用但是卡号不为空
  5404. :return:
  5405. """
  5406. group = Group.get_group(groupId) # type: GroupDict
  5407. dealer = Dealer.objects.get(id = group.ownerId)
  5408. groupIds = Dealer.get_currency_group_ids(dealer, group)
  5409. groupIds.append(groupId)
  5410. objs = cls.objects.filter(
  5411. openId = openId, groupId__in = groupIds, isDisable = 0
  5412. ).filter(
  5413. Q(cardNo = cardNo) | Q(bothCardAndMobile = 1)
  5414. ).order_by('dateTimeAdded')
  5415. result = filter(
  5416. lambda obj: obj.useable,
  5417. map(lambda obj: obj.disable(), objs)
  5418. )
  5419. return result[0] if result else None
  5420. @classmethod
  5421. def get_user_all(cls, openId):
  5422. """
  5423. 获取用户所有的包月套餐规则
  5424. :param openId:
  5425. :return:
  5426. """
  5427. return cls.objects.filter(openId = openId, isDisable = 0)
  5428. @classmethod
  5429. def create_by_template(cls, template, rechargeOrder): # type:(MonthlyPackageTemp, RechargeRecord) -> MonthlyPackage
  5430. subIndex = rechargeOrder.attachParas.get("subTempIndex")
  5431. purchasedMonth = template.subTemplate[subIndex].numOfMonth
  5432. data = {
  5433. "name": template.subTemplate[subIndex].displayName,
  5434. "groupId": rechargeOrder.groupId,
  5435. "openId": rechargeOrder.openId,
  5436. "cardNo": rechargeOrder.attachParas.get("cardNo", ""),
  5437. "bothCardAndMobile": template.bothCardAndMobile,
  5438. "startDateTime": rechargeOrder.dateTimeAdded,
  5439. "expireDateTime": rechargeOrder.dateTimeAdded + relativedelta.relativedelta(months = purchasedMonth),
  5440. "maxCountOfDay": template.maxCountOfDay,
  5441. "maxCount": template.maxCountOfMonth * purchasedMonth,
  5442. "maxTimeOfCount": template.maxTimeOfCount,
  5443. "maxElecOfCount": template.maxElecOfCount,
  5444. }
  5445. obj = cls(**data)
  5446. return obj.save()
  5447. @property
  5448. def useable(self):
  5449. """
  5450. :return:
  5451. """
  5452. if self.isDisable:
  5453. return False
  5454. # 总次数
  5455. if len(self.usedDetail) >= self.maxCount:
  5456. return False
  5457. # 0 表示不限制
  5458. if self.maxCountOfDay == 0:
  5459. return True
  5460. else:
  5461. # 当日的次数超限
  5462. dateStr = self.format_date(datetime.date.today())
  5463. dateUseInfo = self.usedDetail.get(dateStr, list())
  5464. if len(dateUseInfo) >= self.maxCountOfDay:
  5465. return False
  5466. return True
  5467. def deduct(self, order):
  5468. # type:(ConsumeRecord)->None
  5469. """
  5470. 使用一次, 扣除一次额度, 根据订单进行扣除
  5471. :return:
  5472. """
  5473. orderTime = order.dateTimeAdded # type: datetime.datetime
  5474. orderInfo = MonthlyPackageUseInfo(orderNo = order.orderNo, orderTime = orderTime)
  5475. updateData = {
  5476. "inc__usedTotal": 1,
  5477. "add_to_set__usedDetail__{}".format(self.format_date(orderTime)): orderInfo
  5478. }
  5479. return self.update(**updateData)
  5480. def rollback(self, order):
  5481. """
  5482. 用于抵扣回退 目前存在与5分钟以内 需要全退的情况
  5483. """
  5484. orderTime = order.dateTimeAdded # type: datetime.datetime
  5485. orderInfo = MonthlyPackageUseInfo(orderNo=order.orderNo, orderTime=orderTime)
  5486. updateData = {
  5487. "dec__usedTotal": 1,
  5488. "pull__usedDetail__{}".format(self.format_date(orderTime)): orderInfo
  5489. }
  5490. return self.update(**updateData)
  5491. def disable(self):
  5492. """
  5493. 检查将 过期的包月套餐直接清理掉
  5494. :return:
  5495. """
  5496. if self.expireDateTime <= datetime.datetime.now():
  5497. self.update(isDisable = 1)
  5498. return self.reload()
  5499. return self
  5500. def is_suit_package(self, package):
  5501. """
  5502. 判断当前的包月套餐是否试用用户选择的套餐
  5503. 时间和电量的 检验是否足够抵扣 单次的直接抵扣一次 其余的暂不支持
  5504. :param package:
  5505. :return:
  5506. """
  5507. if package.get("unit") == u"分钟":
  5508. unit = "Time"
  5509. elif package.get("unit") == u"度":
  5510. unit = "Elec"
  5511. elif package.get("unit") == u"次":
  5512. return True
  5513. else:
  5514. return False
  5515. # 这个地方默认的规则时 如果没有适配到合适的套餐 就直接判定为不能够使用
  5516. count = getattr(self, "max{}OfCount".format(unit), 0)
  5517. # 添加一种情况(当count为0时候) 不做限制,只用次数
  5518. if count == 0:
  5519. return True
  5520. if count >= int(package.get("time", 0xFFFF)):
  5521. return True
  5522. return False
  5523. def to_dict(self):
  5524. dayUse = len(self.usedDetail.get(self.format_date(date = datetime.date.today()), list()))
  5525. return {
  5526. "id": self.id,
  5527. "name": self.name,
  5528. "expireDateTime": self.expireDateTime.strftime("%Y-%m-%d %H:%M:%S"),
  5529. "dayLeft": self.maxCountOfDay - dayUse,
  5530. "totalLeft": self.maxCount - self.usedTotal,
  5531. "maxElecOfCount": self.maxElecOfCount,
  5532. "maxTimeOfCount": self.maxTimeOfCount,
  5533. }
  5534. def get_used_detail(self):
  5535. usedDetail = list()
  5536. for _k, _v in self.usedDetail.items():
  5537. usedDetail.extend([_m.to_dict() for _m in _v])
  5538. usedDetail.sort(key = lambda x: x["orderTime"])
  5539. return usedDetail
  5540. @classmethod
  5541. def get_user_ticket(cls, openId, groupId, cardNo = ""):
  5542. group = Group.get_group(groupId) # type: GroupDict
  5543. dealer = Dealer.objects.get(id = group.ownerId)
  5544. groupIds = Dealer.get_currency_group_ids(dealer, group)
  5545. groupIds.append(groupId)
  5546. objs = cls.objects.filter(
  5547. openId = openId, groupId__in = groupIds, isDisable = 0
  5548. ).filter(
  5549. Q(cardNo = cardNo) | Q(bothCardAndMobile = 1)
  5550. ).order_by('dateTimeAdded')
  5551. return map(lambda obj: obj.disable(), objs)
  5552. @staticmethod
  5553. def get_can_use_one(all_tickets, package):
  5554. # type: (List[MonthlyPackage], dict) -> (MonthlyPackage)
  5555. for ticket in all_tickets:
  5556. if ticket.useable and ticket.is_suit_package(package):
  5557. return ticket
  5558. return None
  5559. class Redpack(Searchable):
  5560. # 红包信息
  5561. # TODO 建立索引 factoryCode(索引), openId(索引)
  5562. # 红包活动类型
  5563. class RedpackType():
  5564. LAXIN = 'laxin'
  5565. RUHUI = 'ruhui'
  5566. class Result(IterConstant):
  5567. FINISHED = 'finished'
  5568. PROCESSING = 'processing'
  5569. openId = StringField(verbose_name='openId', default='')
  5570. title = StringField(verbose_name='红包标题', default='')
  5571. factoryCode = StringField(verbose_name='TaskId')
  5572. redpackType = StringField(verbose_name='红包类型', default='')
  5573. # 红包自身流程
  5574. money = MonetaryField(verbose_name='红包金额', default=RMB('0.00'))
  5575. leastPayMoney = MonetaryField(verbose_name='最小使用金额', default=RMB('0.00'))
  5576. effectTime = DateTimeField(verbose_name='红包生效时间')
  5577. expiredTime = DateTimeField(verbose_name='红包过期时间')
  5578. usedStatus = BooleanField(verbose_name='是否使用', default=False)
  5579. usedTime = DateTimeField(verbose_name='使用时间')
  5580. consumeRecordId = StringField(verbose_name='关联消费订单')
  5581. package = DictField(verbose_name='选中的套餐')
  5582. # 来源信息
  5583. gateway = StringField(verbose_name='平台', default='')
  5584. logicalCode = StringField(verbose_name='设备号', default='')
  5585. devNo = StringField(verbose_name='IMEI', default='')
  5586. dateTimeAdded = DateTimeField(verbose_name='记录添加时间', default=datetime.datetime.now)
  5587. extra = DictField(verbose_name='其他', default={})
  5588. taskStatus = StringField(verbose_name="任务当前状态", default=Result.PROCESSING)
  5589. showType = StringField(verbose_name="红包展示方式")
  5590. meta = {
  5591. "db_alias": "default"
  5592. }
  5593. # 红包业务流程
  5594. @classmethod
  5595. def pre_deducted_coins(cls, redpackId, package):
  5596. # 预计抵扣的金额计算, 此函数目前只用于蓝牙流程
  5597. redpack = cls.objects.filter(id=redpackId, usedStatus=False).first()
  5598. if not redpack:
  5599. return 0.0
  5600. if redpack.money > RMB(package['price']):
  5601. return round(RMB(package['coins']), 2)
  5602. else:
  5603. ratio = Ratio(float(package['coins']) / float(package['price']))
  5604. return round(RMB(redpack.money) * ratio, 2)
  5605. @property
  5606. def to_deducted_coins(self):
  5607. if not self.package:
  5608. return 0.0
  5609. if self.money > RMB(self.package['price']):
  5610. return round(RMB(self.package['coins']), 2)
  5611. else:
  5612. ratio = Ratio(float(self.package['coins']) / float(self.package['price']))
  5613. return round(RMB(self.money) * ratio, 2)
  5614. @property
  5615. def to_deducted_money(self):
  5616. if not self.package:
  5617. return self.money
  5618. else:
  5619. if self.money > RMB(self.package['price']):
  5620. return round(RMB(self.package['price']), 2)
  5621. else:
  5622. return round(RMB(self.money), 2)
  5623. @staticmethod
  5624. def use_redpack(redPacketId, consumeRecordId, package):
  5625. # type: (str, str, dict) -> int
  5626. # 1 校验红包(过期, 状态)
  5627. nowTime = datetime.datetime.now()
  5628. return Redpack.objects.filter(id=redPacketId, usedStatus=False).update(usedStatus=True, usedTime=nowTime,
  5629. consumeRecordId=consumeRecordId, package=package)
  5630. @classmethod
  5631. def get_redpack_list(cls, filtetr, rmb=None):
  5632. """
  5633. rmb 当前使用金额, 如果没有的话
  5634. 例: 红包 满2 - 0.2 最小使用金额必须
  5635. """
  5636. nowTime = datetime.datetime.now()
  5637. if not rmb:
  5638. rmb = RMB(0).mongo_amount
  5639. exp_time = nowTime + datetime.timedelta(minutes=30) # 给30分钟时间给设备启动留用
  5640. redPackets = cls.objects.filter(
  5641. effectTime__lte=nowTime,
  5642. expiredTime__gte=exp_time,
  5643. leastPayMoney__lte=rmb,
  5644. usedStatus=False,
  5645. taskStatus=cls.Result.FINISHED,
  5646. **filtetr
  5647. ).order_by('expiredTime')
  5648. if not redPackets:
  5649. return []
  5650. return list(map(lambda x: x.to_dict(), redPackets))
  5651. def to_dict(self):
  5652. return {
  5653. 'openId': self.openId,
  5654. 'title': self.title,
  5655. 'factoryCode': self.factoryCode,
  5656. 'money': RMB(self.money).mongo_amount,
  5657. 'leastPayMoney': RMB(self.leastPayMoney).mongo_amount,
  5658. 'effectTime': self.effectTime,
  5659. 'expiredTime': self.expiredTime,
  5660. 'usedStatus': self.usedStatus,
  5661. 'logicalCode': self.logicalCode,
  5662. 'consumeRecordId': self.consumeRecordId,
  5663. 'redpackCoins': self.to_deducted_coins,
  5664. 'redpackMoney': self.to_deducted_money,
  5665. 'id': str(self.id),
  5666. 'taskStatus': self.taskStatus,
  5667. 'showType': self.showType,
  5668. }
  5669. def rollback_redpack(self, redPacketId):
  5670. redPacket = self.objects.filter(id=redPacketId).first()
  5671. if not Redpack:
  5672. return False
  5673. return redPacket.update(usedStatus=False)
  5674. @classmethod
  5675. def can_use(cls, dealer, devTypeCode):
  5676. # type: (Dealer, str)->bool
  5677. dealer_features = map(lambda x: x['key'], filter(lambda x: x['value'] == True, dealer.feature_list))
  5678. if 'disable_redpack' in dealer_features:
  5679. return False
  5680. else:
  5681. if devTypeCode and devTypeCode in Redpack.list_of_devices_that_support_redpack():
  5682. return True
  5683. return False
  5684. @classmethod
  5685. def auto_suit_with_money(cls, query, rmb):
  5686. """
  5687. 自动挑选合适的红包抵扣支付金额
  5688. # 有两种判断:
  5689. 1 红包金额大于支付金额, 优先选择与红包金额接近金额支付的红包使用
  5690. 2 红包金额小于支付金额, 优先选择大的红包金额
  5691. """
  5692. try:
  5693. redpacks = cls.get_redpack_list(query)
  5694. if redpacks:
  5695. # 第一种情况做筛选 红包金额大于支付金额, 优先选择与红包金额接近金额支付的红包使用
  5696. _enough_list = filter(lambda _: RMB(_.get('money')) >= rmb, redpacks)
  5697. if _enough_list:
  5698. return min(_enough_list, key=lambda _: RMB(_.get('money', 0)))
  5699. # 第二种情况 没有足够支付的金额 选取最大的红包进行抵扣
  5700. return max(redpacks, key=lambda _: RMB(_.get('money', 0)))
  5701. except:
  5702. import traceback
  5703. logger.error(traceback.format_exc())
  5704. return
  5705. @classmethod
  5706. def auto_suit_with_coins(cls, query, userBalance, package):
  5707. """
  5708. 自动挑选合适的红包抵扣金币
  5709. # 有三种判断:
  5710. 1 红包足以 抵扣 挑选最小的红包
  5711. 2 红包 + 金币 抵扣 挑选最小的红包
  5712. 2 返回 都不足以抵扣 返回空
  5713. """
  5714. try:
  5715. package_money = RMB(package.get('price', 0))
  5716. if package_money == RMB(0):
  5717. return
  5718. package_coins = RMB(package.get('coins', 0))
  5719. ratio = Ratio(float(package.get('coins', 0.0)) / float(package.get('price', 0.0)))
  5720. redpacks = cls.get_redpack_list(query)
  5721. if redpacks:
  5722. # 第一种情况 红包足以 抵扣 挑选最小的红包
  5723. _enough_list = filter(lambda _: RMB(_.get('money')) >= package_money, redpacks)
  5724. if _enough_list:
  5725. return min(_enough_list, key=lambda _: RMB(_.get('money', 0)))
  5726. # 第二种情况 红包 + 金币 抵扣 挑选最小的红包
  5727. _mix_enough_list = filter(lambda _: RMB(_.get('money', 0)) * ratio + RMB(userBalance) >= package_coins, redpacks)
  5728. if _mix_enough_list:
  5729. return min(_mix_enough_list, key=lambda _: RMB(_.get('money', 0)))
  5730. except:
  5731. import traceback
  5732. logger.error(traceback.format_exc())
  5733. return
  5734. @classmethod
  5735. def get_one(self, redpackId):
  5736. redpack = Redpack.objects.filter(id=redpackId).first() # type: Redpack
  5737. if not redpack:
  5738. return {}
  5739. else:
  5740. return redpack.to_dict()
  5741. @classmethod
  5742. def show_redpack(cls, openId):
  5743. nowTime = datetime.datetime.now()
  5744. exp_time = nowTime - datetime.timedelta(days=3)
  5745. # 筛选出过期时间超过三天或者 使用后时间超过三天的红包
  5746. redPackets = cls.objects.filter(Q(expiredTime__gte=exp_time) | Q(usedTime__gte=exp_time),
  5747. openId=openId, taskStatus=cls.Result.FINISHED).order_by('-expiredTime')
  5748. if not redPackets:
  5749. return []
  5750. return list(map(lambda x: x.to_dict(), redPackets))
  5751. @staticmethod
  5752. def list_of_devices_that_support_redpack():
  5753. from apps.web.core.models import SystemSettings
  5754. support_redpack_list = SystemSettings.get_support_redpack_list()
  5755. return support_redpack_list
  5756. @staticmethod
  5757. def add_device_to_support_redpack_list(devTypeCode):
  5758. from apps.web.core.models import SystemSettings
  5759. support_redpack_list = SystemSettings.get_support_redpack_list()
  5760. support_redpack_list.append(devTypeCode)
  5761. support_redpack_list = list(set(support_redpack_list))
  5762. SystemSettings.set_support_redpack_list(support_redpack_list)
  5763. logger.info('Current support redpck list:{}'.format(support_redpack_list))
  5764. @staticmethod
  5765. def remove_device_to_support_redpack_list(devTypeCode):
  5766. from apps.web.core.models import SystemSettings
  5767. support_redpack_list = SystemSettings.get_support_redpack_list()
  5768. if devTypeCode in support_redpack_list:
  5769. support_redpack_list.remove(devTypeCode)
  5770. support_redpack_list = list(set(support_redpack_list))
  5771. SystemSettings.set_support_redpack_list(support_redpack_list)
  5772. logger.info('Current support redpck list:{}'.format(support_redpack_list))
  5773. @staticmethod
  5774. def renew_support_repack_list(devTypeCodeList):
  5775. from apps.web.core.models import SystemSettings
  5776. SystemSettings.set_support_redpack_list(devTypeCodeList)
  5777. # 拉新创建红包*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  5778. @staticmethod
  5779. def create_redpack_by_laxin(factoryCode, openId, money, leastPayMoney, effectTime, expiredTime, gateway,
  5780. logicalCode, devNo, extra):
  5781. if Redpack.objects.filter(factoryCode=factoryCode, openId=openId).first():
  5782. return
  5783. else:
  5784. red_packet = {
  5785. 'factoryCode': factoryCode,
  5786. 'title': '支付宝每日任务红包',
  5787. 'openId': openId,
  5788. 'money': money,
  5789. 'leastPayMoney': leastPayMoney,
  5790. 'effectTime': effectTime,
  5791. 'expiredTime': expiredTime,
  5792. 'gateway': gateway,
  5793. 'logicalCode': logicalCode,
  5794. 'devNo': devNo,
  5795. 'extra': extra,
  5796. 'taskStatus': Redpack.Result.FINISHED,
  5797. 'redpackType': Redpack.RedpackType.LAXIN
  5798. }
  5799. try:
  5800. redpack = Redpack.objects.create(**red_packet)
  5801. except:
  5802. return None
  5803. return redpack
  5804. # 入会创建红包*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  5805. @staticmethod
  5806. def create_redpack_by_ruhui(taskId, openId, urlId, money, leastPayMoney, gateway, logicalCode, devNo, extra, showType=''):
  5807. redpack = Redpack.objects.filter(factoryCode=taskId, openId=openId, redpackType=Redpack.RedpackType.RUHUI,
  5808. taskStatus__ne=Redpack.Result.FINISHED).first()
  5809. if redpack:
  5810. redpack.update(**{
  5811. 'logicalCode': logicalCode,
  5812. 'devNo': devNo,
  5813. 'extra': extra,
  5814. 'taskStatus': Redpack.Result.PROCESSING,
  5815. 'expiredTime': datetime.datetime.now() + datetime.timedelta(days=7),
  5816. 'urlId': urlId,
  5817. 'dateTimeAdded': datetime.datetime.now(),
  5818. 'showType':showType,
  5819. })
  5820. else:
  5821. red_packet = {
  5822. 'factoryCode': taskId,
  5823. 'title': '支付宝每日任务红包.',
  5824. 'openId': openId,
  5825. 'money': money,
  5826. 'leastPayMoney': leastPayMoney,
  5827. 'gateway': gateway,
  5828. 'logicalCode': logicalCode,
  5829. 'devNo': devNo,
  5830. 'extra': extra,
  5831. 'taskStatus': Redpack.Result.PROCESSING,
  5832. 'expiredTime': datetime.datetime.now() + datetime.timedelta(days=7),
  5833. 'redpackType': Redpack.RedpackType.RUHUI,
  5834. 'urlId': urlId,
  5835. 'showType': showType,
  5836. }
  5837. try:
  5838. redpack = Redpack.objects.create(**red_packet)
  5839. except:
  5840. return
  5841. return redpack
  5842. @staticmethod
  5843. def take_effect(openId, urlId, **kw):
  5844. try:
  5845. obj = Redpack.objects.filter(openId=openId, urlId=urlId, redpackType=Redpack.RedpackType.RUHUI,
  5846. taskStatus__ne=Redpack.Result.FINISHED).first()
  5847. if not obj:
  5848. return
  5849. extra = obj.extra
  5850. extra.update(kw)
  5851. obj.update(
  5852. extra=extra,
  5853. taskStatus=Redpack.Result.FINISHED,
  5854. effectTime=datetime.datetime.now(),
  5855. expiredTime=datetime.datetime.now() + datetime.timedelta(days=15)
  5856. )
  5857. return obj.reload()
  5858. except:
  5859. import traceback
  5860. logger.error(traceback.format_exc())
  5861. class OneCardGateLog(Searchable):
  5862. devNo = StringField(verbose_name=u"设备编号")
  5863. logicalCode = StringField(verbose_name=u"逻辑编号")
  5864. openId = StringField(verbose_name=u"用户信息")
  5865. control = IntField(verbose_name=u"进出标记")
  5866. result = BooleanField(verbose_name=u"操纵结果")
  5867. dateTimeAdded = DateTimeField(verbose_name=u"添加时间")
  5868. meta = {
  5869. 'collection': 'OneCardGateLog',
  5870. 'db_alias': 'logdata'
  5871. }
  5872. # ------------------------------------ 以下为新增 ------------------------
  5873. class OrderPackage(dict):
  5874. def belong_category(self, category): # type: (str) -> bool
  5875. return self.category == category
  5876. @property
  5877. def policyType(self): # type: () -> Optional[str, None]
  5878. return self.get("policyType") or self.get("category")
  5879. @property
  5880. def category(self): # type: () -> str
  5881. """
  5882. 套餐的种类 实际上也是相应的启动方式
  5883. """
  5884. if self.policyType:
  5885. return self.policyType
  5886. unit = self.get("unit")
  5887. if unit in [u"度"]:
  5888. return PackageCategory.ELEC
  5889. if unit in [u"元"]:
  5890. return PackageCategory.COIN
  5891. if unit in [u"小时", u"分钟"]:
  5892. return PackageCategory.TIME
  5893. return "unknown"
  5894. @property
  5895. def isPostpaid(self):
  5896. return self.get("isPostpaid", False)
  5897. @property
  5898. def autoRefund(self):
  5899. return self.get("autoRefund", False)
  5900. @property
  5901. def autoStop(self):
  5902. return self.get("autoStop", False)
  5903. @property
  5904. def minFee(self):
  5905. return RMB(self.get("minFee", 0))
  5906. @property
  5907. def minAfterStartCoins(self):
  5908. return RMB(self.get("minAfterStartCoins", 0))
  5909. @property
  5910. def isFree(self):
  5911. return self.get("isFree", False)
  5912. @property
  5913. def price(self):
  5914. """
  5915. 套餐的价格 后付费 即先用后付的情况 显示价格为0
  5916. """
  5917. if self.isFree or self.isPostpaid:
  5918. return RMB(0)
  5919. return RMB(self.get("price", 0))
  5920. @property
  5921. def time(self): # type:()->int
  5922. """
  5923. 套餐的时间单位 最终为 分钟
  5924. 注意区分是否已经初始化过了
  5925. """
  5926. value = self.get("m_time")
  5927. if value:
  5928. return value
  5929. # 初始化过程
  5930. value = self.get('time', 0) if self.belong_category(PackageCategory.TIME) else 0
  5931. if self.get("unit") == u"小时":
  5932. return int(float(value) * 60)
  5933. if self.get("unit") == u"分钟":
  5934. return int(value)
  5935. return 0
  5936. @property
  5937. def elec(self):
  5938. """
  5939. 套餐的电量单位 最终为 度
  5940. """
  5941. value = self.get("m_elec")
  5942. if value:
  5943. return value
  5944. return float(self.get('time', 0) if self.belong_category(PackageCategory.ELEC) else 0)
  5945. @property
  5946. def coin(self):
  5947. """
  5948. 注意 这个coin不是价格的意思 指的是直接是设备运行的硬币数 例如投币 或者某些直接以金额启动设备的套餐
  5949. """
  5950. value = self.get("m_coin")
  5951. if value:
  5952. return value
  5953. return int(self.get('time', 0) if self.belong_category(PackageCategory.COIN) else 0)
  5954. @property
  5955. def name(self):
  5956. return self.get("name", "")
  5957. @property
  5958. def desc(self):
  5959. return self.get("desc", "")
  5960. @property
  5961. def rules(self):
  5962. """
  5963. 即计费规则
  5964. """
  5965. return self.get("rules") or None
  5966. @property
  5967. def refundProtectTime(self):
  5968. return self.get("refundProtectTime", 0)
  5969. @property
  5970. def dumpDict(self):
  5971. """
  5972. 需要被订单固化的数据
  5973. """
  5974. return {
  5975. "category": self.category,
  5976. "name": self.name,
  5977. "price": self.price.mongo_amount,
  5978. "m_time": self.time,
  5979. "m_elec": self.elec,
  5980. "m_coin": self.coin,
  5981. "minAfterStartCoins": self.minAfterStartCoins.mongo_amount,
  5982. "minFee": self.minFee.mongo_amount,
  5983. "autoRefund": self.autoRefund,
  5984. "autoStop": self.autoStop,
  5985. "rules": self.rules,
  5986. "isFree": self.isFree,
  5987. "isPostpaid": self.isPostpaid,
  5988. "desc": self.desc,
  5989. "refundProtectTime": self.refundProtectTime
  5990. }
  5991. @property
  5992. def showDict(self):
  5993. return {
  5994. "category": self.category,
  5995. "name": self.name,
  5996. "time": self.time,
  5997. "elec": self.elec,
  5998. "coin": self.coin,
  5999. "minAfterStartCoins": self.minAfterStartCoins.mongo_amount,
  6000. "minFee": self.minFee.mongo_amount,
  6001. "autoRefund": self.autoRefund,
  6002. "autoStop": self.autoStop,
  6003. "rules": self.rules,
  6004. "isFree": self.isFree,
  6005. "isPostpaid": self.isPostpaid,
  6006. "desc": self.desc,
  6007. "refundProtectTime": self.refundProtectTime
  6008. }
  6009. class PaymentInfo(dict):
  6010. @property
  6011. def deduct_list(self):
  6012. return self.get("deduct_list") or list()
  6013. @property
  6014. def time(self):
  6015. return self.get("time")
  6016. @time.setter
  6017. def time(self, value):
  6018. self.update({"time": value})
  6019. @property
  6020. def isPaid(self):
  6021. return self.time is not None
  6022. @property
  6023. def via(self):
  6024. return self.get("via")
  6025. @property
  6026. def totalAmount(self):
  6027. s = RMB(0)
  6028. for _item in self.deduct_list:
  6029. s += RMB(_item.get("chargeBalance"))
  6030. s += RMB(_item.get("bestowBalance"))
  6031. return s
  6032. @property
  6033. def actualAmount(self):
  6034. """
  6035. 排除赠送余额之后的支付金额
  6036. """
  6037. s = RMB(0)
  6038. for _item in self.deduct_list:
  6039. s += RMB(_item.get("chargeBalance"))
  6040. return s
  6041. @property
  6042. def bestowAmount(self):
  6043. s = RMB(0)
  6044. for _item in self.deduct_list:
  6045. s += RMB(_item.get("bestowBalance"))
  6046. return s
  6047. class ServiceInfo(dict):
  6048. """
  6049. 和设备运行的一切相关信息
  6050. """
  6051. @property
  6052. def deviceStartTime(self):
  6053. return self.get(ConsumeOrderServiceItem.START_TIME)
  6054. @deviceStartTime.setter
  6055. def deviceStartTime(self, value):
  6056. if isinstance(value, datetime.datetime):
  6057. value = value.strftime("%Y-%m-%d %H:%M:%S")
  6058. self.update({ConsumeOrderServiceItem.START_TIME: value})
  6059. @property
  6060. def elec(self):
  6061. return self.get(ConsumeOrderServiceItem.ELEC)
  6062. @elec.setter
  6063. def elec(self, value):
  6064. self.update({ConsumeOrderServiceItem.ELEC: float(value)})
  6065. @property
  6066. def duration(self):
  6067. return self.get(ConsumeOrderServiceItem.DURATION)
  6068. @duration.setter
  6069. def duration(self, value):
  6070. self.update({ConsumeOrderServiceItem.DURATION: int(value)})
  6071. @property
  6072. def deviceEndTime(self):
  6073. return self.get(ConsumeOrderServiceItem.END_TIME)
  6074. @deviceEndTime.setter
  6075. def deviceEndTime(self, value):
  6076. if isinstance(value, datetime.datetime):
  6077. value = value.strftime("%Y-%m-%d %H:%M:%S")
  6078. self.update({ConsumeOrderServiceItem.END_TIME: value})
  6079. @property
  6080. def maxPower(self):
  6081. return self.get(ConsumeOrderServiceItem.MAX_POWER)
  6082. @maxPower.setter
  6083. def maxPower(self, value):
  6084. self.update({ConsumeOrderServiceItem.MAX_POWER: value})
  6085. @property
  6086. def reason(self):
  6087. return self.get(ConsumeOrderServiceItem.REASON)
  6088. @reason.setter
  6089. def reason(self, value):
  6090. self.update({ConsumeOrderServiceItem.REASON: value})
  6091. @property
  6092. def spendMoney(self):
  6093. return RMB(self.get(ConsumeOrderServiceItem.SPEND, 0))
  6094. @spendMoney.setter
  6095. def spendMoney(self, value):
  6096. self.update({ConsumeOrderServiceItem.SPEND: RMB(value)})
  6097. class ConsumeRecord(Searchable):
  6098. """
  6099. 用户的消费订单记录
  6100. """
  6101. class Status(IterConstant):
  6102. CREATED = 'created' # 订单创建初始状态 刚刚下单
  6103. WAIT_CONF = 'waitConf' # 用户下完单之后【等待】用户进一步的动作确认
  6104. FINISHED = 'finished' # 订单的结束状态 表示相应信息已经被记录
  6105. WAITING = 'waiting' # 订单的执行等待状态 设备尚未执行该订单
  6106. RUNNING = 'running' # 订单执行运行状态 设备正在执行该订单
  6107. END = "end" # 订单运行结束状态 设备已经执行订单完毕 订单完结
  6108. TIMEOUT = 'timeout' # 订单启动超时状态 设备启动超时 有可能已经启动了
  6109. FAILURE = 'failure' # 订单执行失败状态 设备明确启动失败 订单已经完结
  6110. UNKNOWN = 'unknown' # 订单的未知状态 未知的订单状态
  6111. WAIT_PAY = 'waitPay' # 订单支付的中间状态
  6112. orderNo = StringField(verbose_name=u"订单号", required=True, unique=True)
  6113. sequenceNo = StringField(verbose_name=u"流水号", unique=True)
  6114. logicalCode = StringField(verbose_name=u"设备编号", required=True)
  6115. groupId = StringField(verbose_name=u"设备地址编号", default=None)
  6116. price = MonetaryField(verbose_name=u"订单价格", default=RMB('0'))
  6117. startType = IntField(verbose_name=u"启动方式", choices=StartDeviceType.choices())
  6118. status = StringField(verbose_name=u"状态", default=Status.CREATED)
  6119. ownerId = StringField(verbose_name=u"经销商", required=True)
  6120. remarks = StringField(verbose_name=u"备注(系统、用户)", default='')
  6121. description = StringField(verbose_name=u"订单描述(错误信息)")
  6122. association = DictField(verbose_name=u'关联单', default={})
  6123. serviceInfo = DictField(verbose_name=u"订单的服务信息", default={})
  6124. isFree = BooleanField(verbose_name=u"是否订单免费", default=False)
  6125. dateTimeAdded = DateTimeField(verbose_name=u"创建时间", default=datetime.datetime.now)
  6126. finishedTime = DateTimeField(verbose_name=u"结束时间")
  6127. cardId = StringField(verbose_name=u"卡ID")
  6128. openId = StringField(verbose_name=u"用户", required=True)
  6129. nickname = StringField(verbose_name=u'用户昵称', default='')
  6130. # 以下信息属于订单快照 防止引用被修改
  6131. devNo = StringField(verbose_name=u"设备ID", required=True)
  6132. port = IntField(verbose_name=u"启动端口")
  6133. devTypeName = StringField(verbose_name=u"设备类型名称", default=None)
  6134. devTypeCode = StringField(verbose_name=u"设备类型编码", default=None)
  6135. address = StringField(verbose_name=u"设备地址", default=None)
  6136. groupNumber = StringField(verbose_name=u"设备", default=None)
  6137. groupName = StringField(verbose_name=u"交易场地", default=None)
  6138. # 退款状态应该是用户下单的时候即被锁定 和设备、支付、套餐有关系
  6139. startPackage = DictField(verbose_name=u'启动套餐', required=True)
  6140. paymentInfo = DictField(verbose_name=u'支付的相关信息', default={})
  6141. refundInfo = DictField(verbose_name=u"退还的相关信息", default={})
  6142. dailyStats = LazyReferenceField(verbose_name="统计关联单", document_type='DealerGroupStats')
  6143. feedbackId = ObjectIdField(verbose_name=u'用户反馈ID')
  6144. search_fields = ('openId', 'devNo', 'orderNo', 'remarks', 'logicalCode')
  6145. _shard_key = ('ownerId', 'dateTimeAdded')
  6146. _origin_meta = {
  6147. "collection": "ConsumeRecord",
  6148. "db_alias": "default"
  6149. }
  6150. meta = _origin_meta
  6151. def __str__(self):
  6152. return '{}<id={} orderNo={}>'.format(self.__class__.__name__, str(self.id), self.orderNo)
  6153. @cached_property
  6154. def owner(self):
  6155. # 防止id报错
  6156. if not self.ownerId:
  6157. return None
  6158. from apps.web.dealer.models import Dealer
  6159. dealer = Dealer.objects(id=self.ownerId).first()
  6160. return dealer
  6161. @cached_property
  6162. def user(self): # type:() -> MyUser
  6163. return MyUser.objects.filter(openId=self.openId, groupId=self.groupId).first()
  6164. @cached_property
  6165. def card(self):
  6166. return Card.objects.filter(id=self.cardId).first()
  6167. @property
  6168. def coin(self):
  6169. return self.price
  6170. @property
  6171. def package(self): # type: () -> OrderPackage
  6172. return OrderPackage(self.startPackage)
  6173. @property
  6174. def device(self): # type:()-> DeviceDict
  6175. return Device.get_dev(self.devNo)
  6176. @property
  6177. def group(self): # type:() -> GroupDict
  6178. return Group.get_group(self.groupId)
  6179. @property
  6180. def startLockKey(self):
  6181. return "{}-{}-start-device-lock".format(self.openId, self.orderNo)
  6182. @property
  6183. def isPayTimeOut(self):
  6184. """
  6185. 订单到支付下单的一个过程
  6186. """
  6187. now = datetime.datetime.now()
  6188. return (now - self.dateTimeAdded).total_seconds() > CONSUME_ORDER_PAY_TIMEOUT
  6189. @property
  6190. def isPostPaid(self):
  6191. """
  6192. True 后付费订单 使用完给钱
  6193. False 预付费订单 先给钱再使用然后再决定是否退款
  6194. """
  6195. return self.package.isPostpaid
  6196. @property
  6197. def isStartNetPay(self):
  6198. return self.startType == StartDeviceType.ON_LIEN
  6199. @property
  6200. def isStartCardPay(self):
  6201. return self.startType == StartDeviceType.CARD
  6202. @property
  6203. def payer(self):
  6204. if self.startType == StartDeviceType.ON_LIEN:
  6205. return self.user
  6206. else:
  6207. return self.card
  6208. @property
  6209. def payment(self): # type: () -> PaymentInfo
  6210. return PaymentInfo(self.paymentInfo)
  6211. @property
  6212. def refund(self): # type: () -> PaymentInfo
  6213. return PaymentInfo(self.refundInfo)
  6214. @property
  6215. def service(self): # type:() -> ServiceInfo
  6216. return ServiceInfo(self.serviceInfo)
  6217. @property
  6218. def device_start_time(self):
  6219. return self.service.deviceStartTime
  6220. @property
  6221. def device_end_time(self):
  6222. return self.service.deviceEndTime
  6223. @property
  6224. def isPaid(self):
  6225. if self.isFree:
  6226. return True
  6227. return self.payment.isPaid
  6228. @property
  6229. def actualAmount(self):
  6230. """
  6231. 用户订单实际消费的金额
  6232. """
  6233. return self.payment.actualAmount - self.refund.actualAmount
  6234. @property
  6235. def detail_link(self):
  6236. if self.device.deviceAdapter.support_count_down(self.openId, self.port):
  6237. return concat_count_down_page_url(devNo=self.devNo, port=self.port)
  6238. else:
  6239. return concat_front_end_url(uri='/user/index.html#/user/consumeDetail?id={}'.format(str(self.id)))
  6240. @property
  6241. def receiptDesc(self):
  6242. rv = {
  6243. 'orderNo': self.orderNo,
  6244. 'createdTime': self.dateTimeAdded,
  6245. 'payment': u'{} 元'.format(self.price)
  6246. }
  6247. if self.isFree:
  6248. rv.update({"payment": u"本次免费"})
  6249. if self.isPostPaid:
  6250. rv.update({"payment": u"先用后付"})
  6251. return rv
  6252. @property
  6253. def startKey(self):
  6254. return self.orderNo
  6255. @property
  6256. def actualAmount(self):
  6257. return self.payment.actualAmount - self.refund.actualAmount
  6258. @property
  6259. def bestowAmount(self):
  6260. return self.payment.bestowAmount - self.refund.bestowAmount
  6261. @property
  6262. def isNormal(self):
  6263. """
  6264. 是否为正常订单
  6265. """
  6266. if self.status in [self.Status.FAILURE, self.Status.UNKNOWN, self.Status.TIMEOUT]:
  6267. return False
  6268. if self.status in [self.Status.WAIT_CONF, self.Status.CREATED]:
  6269. return False
  6270. return True
  6271. @property
  6272. def device_identity_info(self):
  6273. return {
  6274. 'logicalCode': self.logicalCode,
  6275. 'devTypeCode': self.devTypeCode,
  6276. 'devTypeName': self.devTypeName,
  6277. 'groupName': self.groupName,
  6278. 'groupNumber': self.groupNumber,
  6279. 'address': self.address,
  6280. 'groupId': self.groupId
  6281. }
  6282. @classmethod
  6283. def make_no(cls, *args, **kwargs):
  6284. """
  6285. 有可能会重复 需要保证唯一性
  6286. """
  6287. timestamp = generate_timestamp_ex()
  6288. random_int = random.randint(1, 1000)
  6289. return "%d%03d" % (timestamp, random_int)
  6290. @classmethod
  6291. def new_one(cls, orderNo, user, device, context): # type:(str, MyUser, DeviceDict, StartParamContext) -> ConsumeRecord
  6292. order = cls(
  6293. orderNo=orderNo,
  6294. sequenceNo=context.sequence,
  6295. logicalCode=device.logicalCode,
  6296. groupId=device.groupId,
  6297. price=context.package.price,
  6298. startType=context.startType,
  6299. ownerId=device.ownerId,
  6300. openId=user.openId,
  6301. cardId=context.cardId,
  6302. nickname=user.nickname,
  6303. devNo=device.devNo,
  6304. port=context.port,
  6305. devTypeName=device.devTypeName,
  6306. devTypeCode=device.devTypeCode,
  6307. address=device.group.address,
  6308. groupNumber=device.groupNumber,
  6309. groupName=device.group.groupName,
  6310. startPackage=context.package.dumpDict,
  6311. isFree=context.package.isFree,
  6312. autoRefund=context.package.autoRefund
  6313. )
  6314. return order.save()
  6315. def update_payment(self, payment): # type:(dict) -> bool
  6316. """支付信息添加"""
  6317. if self.isPaid:
  6318. return False
  6319. self.paymentInfo = payment
  6320. return self.save()
  6321. def frozen_payer_balance(self):
  6322. """
  6323. 执行订单的支付过程 实际上是将用户的金额冻结
  6324. """
  6325. if not self.payment:
  6326. return
  6327. if self.isPaid:
  6328. return
  6329. payer, payment = self.payer, self.payment
  6330. result = payer.__class__.freeze_balance(str(self.id), self.payment)
  6331. if not result:
  6332. return
  6333. # 更新一次付款信息
  6334. payment.time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  6335. self.update_payment(payment)
  6336. payer.account_consume(self)
  6337. def clear_payer_frozen(self, refundMoney=RMB(0)):
  6338. from apps.web.user.utils2 import generate_refund
  6339. refundInfo = PaymentInfo(generate_refund(self, refundMoney))
  6340. result = self.payer.__class__.clear_frozen_balance(
  6341. str(self.id),
  6342. self.payment,
  6343. refundInfo
  6344. )
  6345. if not result:
  6346. return
  6347. if refundMoney == RMB(0):
  6348. return
  6349. refundInfo.time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  6350. self.refundInfo = refundInfo
  6351. self.save()
  6352. self.payer.account_refund(self)
  6353. def link_state(self, state):
  6354. self.dailyStats = state
  6355. self.finishedTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  6356. self.save()
  6357. def to_user_detail(self):
  6358. data = {
  6359. "id": str(self.id),
  6360. "orderNo": self.orderNo,
  6361. "ownerId": self.ownerId,
  6362. "money": self.price,
  6363. "createdTime": self.dateTimeAdded,
  6364. "completionTime": self.finishedTime,
  6365. "startResult": True if self.device_start_time else False,
  6366. "errorDesc": self.description,
  6367. "deviceStatTime": self.device_start_time,
  6368. "deviceFinishedTime": self.device_end_time,
  6369. "isRefund": True if self.refund.time else False,
  6370. "orderStatus": self.status
  6371. }
  6372. if self.refund.time:
  6373. data.update({
  6374. "refundedMoney": self.refund.totalAmount
  6375. })
  6376. data.update(self.device_identity_info)
  6377. return data
  6378. def to_detail(self):
  6379. data = {
  6380. "id": str(self.id),
  6381. 'orderNo': self.orderNo,
  6382. 'createdTime': self.dateTimeAdded,
  6383. 'completionTime': self.finishedTime,
  6384. 'deviceStatTime': self.device_start_time,
  6385. 'deviceFinishedTime': self.device_end_time,
  6386. 'amount': self.price,
  6387. 'openId': self.openId,
  6388. 'groupId': self.groupId,
  6389. 'userNickname': u'用户' if not self.nickname else self.nickname,
  6390. 'ownerId': self.ownerId,
  6391. 'devNo': self.devNo,
  6392. 'logicalCode': self.logicalCode,
  6393. 'groupName': self.groupName,
  6394. 'address': self.address,
  6395. 'groupNumber': self.groupNumber,
  6396. 'devType': self.devTypeName, # 兼容
  6397. 'devTypeName': self.devTypeName,
  6398. 'remarks': self.remarks,
  6399. 'startResult': 'success' if self.isNormal else 'failed',
  6400. 'errorDesc': self.description,
  6401. 'servicedInfo': [
  6402. u'%s: %s'.encode('utf-8') % (GLOSSARY_TRANSLATION.get(k, k), v) for k, v in self.service.iteritems()
  6403. ],
  6404. # 以下字段和以前做兼容
  6405. 'finishedTime': self.device_end_time,
  6406. 'port': self.port
  6407. }
  6408. return data
  6409. class BalanceLog(Searchable):
  6410. meta = {
  6411. 'abstract': True,
  6412. }
  6413. bAmount = MonetaryField(verbose_name=u"变动前充值余额", required=True)
  6414. bBestowAmount = MonetaryField(verbose_name=u"变动前赠送余额", required=True)
  6415. aAmount = MonetaryField(verbose_name=u"变动后充值余额", required=True)
  6416. aBestowAmount = MonetaryField(verbose_name=u"变动后赠送金额", required=True)
  6417. # 充值订单 1 退费订单 2 消费订单 3
  6418. category = IntField(verbose_name=u"变动类型", choices=UserBalanceChangeCategory.choices())
  6419. sourceObj = GenericLazyReferenceField(verbose_name=u'资金变动来源', required=True)
  6420. dateTimeAdded = DateTimeField(verbose_name=u"变动时间", default=datetime.datetime.now)
  6421. @cached_property
  6422. def source(self):
  6423. return self.sourceObj.fetch()
  6424. @property
  6425. def beforeBalance(self):
  6426. return self.bAmount + self.bBestowAmount
  6427. @property
  6428. def afterBalance(self):
  6429. return self.aBestowAmount + self.aAmount
  6430. def to_dict(self):
  6431. data = {
  6432. "id": str(self.id),
  6433. "category": self.category,
  6434. "beforeBalance": self.beforeBalance,
  6435. "afterBalance": self.afterBalance,
  6436. "dateTimeAdded": self.dateTimeAdded.strftime("%Y-%m-%d %H:%M:%S"),
  6437. "sourceId": str(self.sourceObj.id)
  6438. }
  6439. return data
  6440. class UserBalanceLog(BalanceLog):
  6441. """
  6442. 用户账户余额变化记录
  6443. """
  6444. openId = StringField(verbose_name=u"资金主体标识", required=True)
  6445. productAgentId = StringField(verbose_name=u"平台标识", required=True)
  6446. @classmethod
  6447. def consume(cls, user, afterAmount, afterBestowAmount, consumeAmount, consumeBestowAmount, order): # type: (MyUser, RMB, RMB, RMB, RMB, ConsumeRecord) -> UserBalanceLog
  6448. return cls(
  6449. openId=user.openId,
  6450. productAgentId=user.productAgentId,
  6451. bAmount=afterAmount+consumeAmount,
  6452. bBestowAmount=afterBestowAmount+consumeBestowAmount,
  6453. aAmount=afterAmount,
  6454. aBestowAmount=afterBestowAmount,
  6455. sourceObj=order,
  6456. category=UserBalanceChangeCategory.CONSUME
  6457. ).save()
  6458. @classmethod
  6459. def recharge(cls, user, afterAmount, afterBestowAmount, chargeAmount, chargeBestowAmount, order):
  6460. return cls(
  6461. openId=user.openId,
  6462. productAgentId=user.productAgentId,
  6463. bAmount=afterAmount-chargeAmount,
  6464. bBestowAmount=afterBestowAmount-chargeBestowAmount,
  6465. aAmount=afterAmount,
  6466. aBestowAmount=afterBestowAmount,
  6467. sourceObj=order,
  6468. category=UserBalanceChangeCategory.RECHARGE
  6469. ).save()
  6470. @classmethod
  6471. def refund(cls, user, afterAmount, afterBestowAmount, refundAmount, refundBestowAmount, order):
  6472. return cls(
  6473. openId=user.openId,
  6474. productAgentId=user.productAgentId,
  6475. bAmount=afterAmount-refundAmount,
  6476. bBestowAmount=afterBestowAmount-refundBestowAmount,
  6477. aAmount=afterAmount,
  6478. aBestowAmount=afterBestowAmount,
  6479. sourceObj=order,
  6480. category=UserBalanceChangeCategory.REFUND
  6481. ).save()
  6482. @classmethod
  6483. def get_logs(cls, user, pageIndex, pageSize): # type:(MyUser, int, int) -> QuerySet
  6484. return cls.objects.filter(
  6485. openId=user.openId,
  6486. productAgentId=user.productAgentId
  6487. ).skip((pageIndex-1)*pageSize).limit(pageSize)
  6488. class CardBalanceLog(BalanceLog):
  6489. cardId = StringField(verbose_name=u"资金主体标识", required=True)
  6490. openId = StringField(verbose_name=u"卡的持有人", required=True)
  6491. @classmethod
  6492. def consume(cls, card, afterAmount, afterBestowAmount, consumeAmount, consumeBestowAmount, order): # type: (Card, RMB, RMB, RMB, RMB, ConsumeRecord) -> UserBalanceLog
  6493. return cls(
  6494. cardId=str(card.id),
  6495. openId=card.openId,
  6496. bAmount=afterAmount + consumeAmount,
  6497. bBestowAmount=afterBestowAmount + consumeBestowAmount,
  6498. aAmount=afterAmount,
  6499. aBestowAmount=afterBestowAmount,
  6500. sourceObj=order,
  6501. category=UserBalanceChangeCategory.CONSUME
  6502. ).save()
  6503. @classmethod
  6504. def recharge(cls, card, afterAmount, afterBestowAmount, chargeAmount, chargeBestowAmount, order):
  6505. return cls(
  6506. cardId=str(card.id),
  6507. openId=card.openId,
  6508. bAmount=afterAmount - chargeAmount,
  6509. bBestowAmount=afterBestowAmount - chargeBestowAmount,
  6510. aAmount=afterAmount,
  6511. aBestowAmount=afterBestowAmount,
  6512. sourceObj=order,
  6513. category=UserBalanceChangeCategory.RECHARGE
  6514. ).save()
  6515. @classmethod
  6516. def refund(cls, card, afterAmount, afterBestowAmount, refundAmount, refundBestowAmount, order):
  6517. return cls(
  6518. cardId=str(card.id),
  6519. openId=card.openId,
  6520. bAmount=afterAmount - refundAmount,
  6521. bBestowAmount=afterBestowAmount - refundBestowAmount,
  6522. aAmount=afterAmount,
  6523. aBestowAmount=afterBestowAmount,
  6524. sourceObj=order,
  6525. category=UserBalanceChangeCategory.REFUND
  6526. ).save()
  6527. @classmethod
  6528. def get_logs(cls, card, pageIndex, pageSize):
  6529. return cls.objects.filter(
  6530. cardId=str(card.id)
  6531. ).skip((pageIndex - 1) * pageSize).limit(pageSize)