12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- """
- web.user.models
- ~~~~~~~~~
- """
- import copy
- import datetime
- import logging
- import random
- import string
- import threading
- import time
- import uuid
- from collections import Counter, defaultdict
- from arrow import Arrow
- from bson.objectid import ObjectId
- from dateutil import relativedelta
- from django.conf import settings
- from django.utils.functional import cached_property
- from mongoengine import Q
- from mongoengine.document import EmbeddedDocument
- from mongoengine.errors import NotUniqueError, DoesNotExist
- from mongoengine.fields import (BooleanField, StringField, IntField, EmbeddedDocumentListField,
- DateTimeField, PointField, DictField, ObjectIdField, ListField, EmbeddedDocumentField,
- MapField, GenericLazyReferenceField, LazyReferenceField)
- from pymongo.errors import DuplicateKeyError
- from pymongo.results import UpdateResult
- from typing import Union, Dict, AnyStr, Optional, TYPE_CHECKING, List, Tuple
- from apilib.monetary import RMB, VirtualCoin, Ratio, Percent, Money
- from apilib.quantity import Quantity
- from apilib.systypes import IterConstant
- from apilib.utils import flatten
- from apilib.utils_datetime import generate_timestamp_ex, today_format_str, to_datetime, get_tomorrow_zero_time
- from apilib.utils_mongo import BulkHandlerEx, dict_field_with_money
- from apilib.utils_string import get_random_str
- from apps import serviceCache
- from apps.web.agent.models import Agent, MoniApp
- from apps.web.common.models import OrderRecordBase, RefundOrderBase
- from apps.web.common.transaction import OrderNoMaker, OrderMainType, UserPaySubType, UserConsumeSubType, RefundSubType
- from apps.web.common.validation import CARD_NO_RE
- from apps.web.constant import (Const,
- DEALER_CONSUMPTION_AGG_KIND, GLOSSARY_TRANSLATION,
- USER_RECHARGE_TYPE,
- RECHARGE_CARD_TYPE,
- AppPlatformType, APP_PLATFORM_TYPE_TRANSLATION,
- RechargeRecordVia, RECHARGE_RECORD_VIA_TRANSLATION, ErrorCode,
- DEVICE_INCOME_STRATEGY, support_policy_device, support_policy_weifule,
- PARTITION_ROLE)
- from apps.web.core import PayAppType, ROLE
- from apps.web.core.bridge.wechat import WechatClientProxy
- from apps.web.core.db import Searchable, MonetaryField, VirtualCoinField, RoleBaseDocument, BooleanIntField
- from apps.web.core.exceptions import InsufficientFundsError, ServiceException, ImproperlyOperatedBalance
- from apps.web.core.models import BoundOpenInfo
- from apps.web.core.models import WechatAuthApp
- from apps.web.dealer.models import Dealer, VirtualCard, DealerDict
- from apps.web.device.models import Group, Device, DeviceType
- from apps.web.exceptions import UserServerException, PostPayOrderError
- from apps.web.report.ledger import AgentLedgerFirst, PartnerLedgerFirst
- from apps.web.report.utils import record_consumption_stats
- from apps.web.user.conf import REFUND_NOTIFY_URL
- from apps.web.user.constant2 import StartDeviceType, PackageCategory, CONSUME_ORDER_PAY_TIMEOUT, ConsumeOrderServiceItem, UserBalanceChangeCategory
- 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
- from library.idgen import IDGenService
- from apps.web.core.payment import PaymentGateway
- logger = logging.getLogger(__name__)
- if TYPE_CHECKING:
- from apps.web.device.models import FeedBack
- from apps.web.device.models import DeviceDict, GroupDict
- from apps.web.common.models import CapitalUser
- from apps.web.dealer.models import MonthlyPackageTemp
- from mongoengine.queryset import QuerySet
- from apps.web.dealer.models import DealerRechargeRecord
- from apps.web.user.utils2 import StartParamContext, check_consume_order_timeout
- class MyUserAuthBackend(object):
- """
- 部分接口需要用户鉴权,由于目前用户体系尚未完整建立,在这里首先实现MVP
- """
- # noinspection PyUnusedLocal
- def authenticate(self, **kwargs):
- return True
- def get_user(self, user_id):
- return self.user_document.objects.with_id(user_id)
- @property
- def user_document(self):
- self._user_doc = MyUser
- return self._user_doc
- class EndUserLocation(EmbeddedDocument):
- logicalCode = StringField(verbose_name = '')
- # point :: { "type" : "Point" , "coordinates" : [longitude, latitude]}
- point = PointField(default = None, verbose_name = u'用户的经纬度')
- type = StringField(verbose_name = u'经纬度类型')
- createdTime = DateTimeField(default = datetime.datetime.now)
- @property
- def coordinates(self):
- if not self.point:
- return ()
- else:
- return tuple(self.point['coordinates'])
- def __repr__(self):
- return '<EndUserLocation (logicalCode=%s, point=(lng=%s, lat=%s), type=%s)>' \
- % (self.logicalCode,
- self.point['coordinates'][0],
- self.point['coordinates'][1],
- self.type)
- class UserMoney(EmbeddedDocument):
- settled = MonetaryField(verbose_name = "已经分账给经销商的金额", default = RMB('0.00')) # type: RMB
- unsettled = MonetaryField(verbose_name = "未分账给经销商的金额", default = RMB('0.00')) # type: RMB
- total_recharged = MonetaryField(default = RMB('0.00')) # type: RMB
- total_consumed = MonetaryField(default = RMB('0.00')) # type: RMB
- def __repr__(self):
- return '<UserMoney settled=%s, unsettled=%s, total_recharged=%s, total_consumed=%s>' \
- % (self.settled, self.unsettled, self.total_recharged, self.total_consumed)
- def to_dict(self):
- return {
- 'settled': self.settled,
- 'unsettled': self.unsettled,
- 'total_recharged': self.total_recharged,
- 'total_consumed': self.total_consumed
- }
- class UserCoin(EmbeddedDocument):
- balance = VirtualCoinField(default = VirtualCoin('0.00'))
- total_recharged = VirtualCoinField(default = VirtualCoin('0.00'))
- total_consumed = VirtualCoinField(default = VirtualCoin('0.00'))
- def __repr__(self):
- return '<UserCoin balance=%s total_recharged=%s total_consumed=%s>' \
- % (self.balance, self.total_recharged, self.total_consumed)
- def to_dict(self):
- return {
- 'balance': self.balance,
- 'total_recharged': self.total_recharged,
- 'total_consumed': self.total_consumed
- }
- class UniqueUser(Searchable):
- openId = StringField(verbose_name='第三方用户ID(微信的OPENID,支付宝的UID)', null=False, unique=True)
- userId = StringField(verbose_name='用户平台UID')
- phone = StringField(verbose_name='电话号码', default="")
- meta = {
- 'collection': 'unique_user',
- 'db_alias': 'default',
- 'indexes': [
- {'fields': ['userId'], 'unique': True, 'sparse': True}
- ]
- }
- @classmethod
- def get_or_create(cls, openId): # type:(str) -> UniqueUser
- """
- 创建或者是获取用户
- """
- user = cls.objects(openId = openId).first()
- if user:
- return user
- try:
- return cls(openId = openId).save()
- except DuplicateKeyError:
- return cls.objects(openId = openId).first()
- def update_phone(self, phone): # type:(str) -> bool
- """
- 更新电话号码
- """
- return bool(self.update(phone=phone))
- class MyUser(RoleBaseDocument):
- """
- `EndUser`
- 终端用户模型
- """
- sex = IntField(verbose_name=u"性别", default=Const.USER_SEX.UNKNOWN)
- phoneOS = StringField(verbose_name=u"终端操作系统", default="")
- city = StringField(verbose_name=u"城市", default="")
- province = StringField(verbose_name=u"省份", default="")
- country = StringField(verbose_name=u"国家", default="")
- avatar = StringField(verbose_name=u"头像地址", default="")
- nickname = StringField(verbose_name=u"名称", max_length=255, default="")
- groupId = StringField(verbose_name=u"地址编号", default="")
- gateway = StringField(verbose_name=u"来自支付宝或微信", default='wechat')
- chargeBalance = MonetaryField(verbose_name=u"余额", default=RMB('0'))
- bestowBalance = MonetaryField(verbose_name=u"赠送余额", default=RMB('0'))
- total_recharged = MonetaryField(verbose_name="累计充值", default=RMB('0'))
- total_bestow = MonetaryField(verbose_name=u"累计赠送", default=RMB('0'))
- total_consumed = VirtualCoinField(verbose_name="累计消费", default=RMB('0'))
- phoneNumber = StringField(verbose_name=u"用户手机号码", default="")
- authAppId = StringField(verbose_name=u"鉴权appid", default="")
- openId = StringField(verbose_name=u"openID|支付宝buyerID", default="")
- unionId = StringField(verbose_name=u"统一用户ID", default="")
- managerialAppId = StringField(verbose_name=u"管理公众号AppId", default="")
- managerialOpenId = StringField(verbose_name=u"管理openId", default="")
- payAppId = StringField(verbose_name=u"最近登录微信授权appid", default="")
- payOpenId = StringField(verbose_name=u"openId", default="")
- payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo))
- extra = DictField(verbose_name=u"多余字段", default={})
- dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'添加进来的时间')
- last_login = DateTimeField(default=datetime.datetime.now, verbose_name=u'最近登录时间')
- lastLoginUserAgent = StringField(verbose_name=u"最后一次登录的userAgent", default="")
- locations = EmbeddedDocumentListField(document_type=EndUserLocation)
- agentId = StringField(verbose_name=u'当前用户绑定的直接上级代理商', default='')
- productAgentId = StringField(verbose_name=u'当前用户绑定的平台代理商', default='')
- promo = DictField(verbose_name=u'活动相关信息', default={})
- favoriteDeviceList = ListField(verbose_name=u'收藏的宝贝', default=[])
- smsVendor = StringField(verbose_name=u'sms提供商', default='')
- ongoingList = ListField(field=DictField(), verbose=u'冻结的金额')
- blacklistConfig = DictField(verbose_name=u'黑名单相关设置', default={})
- meta = {
- "collection": "MyUser2",
- "db_alias": "default",
- "indexes": [{
- 'fields': ['$openId', '$nickname'],
- 'weights': {'openId': 10, 'nickname': 2}
- },
- 'openId',
- 'nickname',
- 'dateTimeAdded',
- # 'groupId'
- ],
- # "shard_key": ('openId',)
- }
- search_fields = ('openId', 'nickname')
- _AGENT_GROUP_ID_PREFIX = 'agent_'
- __mgr_cache = serviceCache
- def __str__(self):
- return '{}<id={} openId={} groupId={}>'.format(self.__class__.__name__, str(self.id), self.openId, self.groupId)
- @property
- def balance(self):
- return self.chargeBalance + self.bestowBalance
- def pay(self, payment): # type:(PaymentInfo) -> PaymentInfo
- if not payment:
- return
- if not payment.deduct_list:
- return
- # 虽然是循环扣除 但是一般情况下只会扣除1次
- bulker = BulkHandlerEx(self.__class__.get_collection()) # type: BulkHandlerEx
- chargeBalanceField = self.__class__.chargeBalance.name
- bestowBalanceField = self.__class__.bestowBalance.name
- for deduct in payment.deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- }
- update = {
- '$inc': {
- chargeBalanceField: (-RMB(deduct[chargeBalanceField])).mongo_amount,
- bestowBalanceField: (-RMB(deduct[bestowBalanceField])).mongo_amount
- }
- }
- bulker.update(query, update)
- result = bulker.execute()
- if result['success'] == 0 or len(result['info']['writeErrors']) != 0:
- logger.error("[user pay] pay error, result = {}".format(result))
- else:
- # 添加支付时间并返回
- payment.time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- return payment
- def account_consume(self, order): # type:(ConsumeRecord) -> None
- payment = order.payment
- # 获取支付的钱
- consumeAmount = payment.actualAmount
- consumeBestowAmount = payment.totalAmount - consumeAmount
- # 获取平台余额
- charge, bestow, _, __ = self.filter_my_balance()
- UserBalanceLog.consume(
- order.user,
- afterAmount=charge,
- afterBestowAmount=bestow,
- consumeAmount=consumeAmount,
- consumeBestowAmount=consumeBestowAmount,
- order=order
- )
- def account_refund(self, order): # type:(ConsumeRecord) -> None
- refund = order.refund
- # 获取支付的钱
- refundAmount = refund.actualAmount
- refundBestowAmount = refund.totalAmount - refundAmount
- # 获取平台余额
- charge, bestow, _, __ = self.filter_my_balance()
- UserBalanceLog.refund(
- order.user,
- afterAmount=charge,
- afterBestowAmount=bestow,
- refundAmount=refundAmount,
- refundBestowAmount=refundBestowAmount,
- order=order
- )
- def account_recharge(self, order): # type:(RechargeRecord) -> None
- # 获取支付的钱
- chargeAmount = order.chargeAmount
- bestowAmount = order.bestowAmount
- # 获取平台余额
- charge, bestow, _, __ = self.filter_my_balance()
- UserBalanceLog.recharge(
- order.user,
- afterAmount=charge,
- afterBestowAmount=bestow,
- chargeAmount=chargeAmount,
- chargeBestowAmount=bestowAmount,
- order=order
- )
- def is_authenticated(self):
- if not self.authAppId:
- return False
- return True
- def get_promo_info(self, key):
- return self.promo.get(key)
- def set_promo_info(self, key, value):
- promo = self.promo
- promo[key] = value
- return self.update(promo = promo)
- @property
- def inhouse_promo_openId(self):
- return self.get_promo_info("inhouse_openId")
- def set_inhouse_promo_openId(self, openId):
- return self.set_promo_info(key = "inhouse_openId", value = openId)
- @property
- def feature_keys(self):
- return ['phoneOS', 'sex', 'gateway']
- @property
- def feature_map(self):
- return {'phoneOS': self.phoneOS, 'sex': self.sex_in_en, 'gateway': self.gateway}
- @property
- def sex_in_en(self):
- if self.is_female:
- return 'female'
- elif self.is_male:
- return 'male'
- else:
- return ''
- @property
- def is_female(self):
- return self.sex == Const.USER_SEX.FEMALE
- @property
- def is_male(self):
- return self.sex == Const.USER_SEX.MALE
- def get_bound_pay_openid(self, key):
- # type: (str)->str
- if self.gateway == AppPlatformType.ALIPAY:
- return self.openId
- else:
- pay_openid_map = self.payOpenIdMap # type: dict
- bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) # type: BoundOpenInfo
- return bound.openId
- def set_bound_pay_openid(self, key, **payload):
- # type: (str, Dict)->None
- self.payOpenIdMap[key] = BoundOpenInfo(**payload)
- @classmethod
- def _product_group_id(cls, agent_id):
- # type:(str)->str
- return '{}{}'.format(cls._AGENT_GROUP_ID_PREFIX, agent_id)
- @classmethod
- def _is_product_group_id(cls, group_id):
- # type: (str)->bool
- return group_id.startswith(cls._AGENT_GROUP_ID_PREFIX)
- def is_product_user(self):
- return not self.groupId or self.groupId.startswith(self._AGENT_GROUP_ID_PREFIX)
- @classmethod
- def get_product_users(cls, agent_id): # type: (str) -> QuerySet
- productGroupId = cls._product_group_id(agent_id)
- return cls.objects.filter(groupId=productGroupId)
- @classmethod
- def get_or_create(cls, app_platform_type, open_id, group_id=groupId.default, **kwargs):
- # type: (str, str, str, Dict)->MyUser
- logger.info('get_or_create cls = {}, app_platform_type = {}, open_id = {}, groupId = {}, kwargs = {}'.format(
- cls, app_platform_type, open_id, group_id, str(kwargs)))
- if app_platform_type in [AppPlatformType.ALIPAY]:
- kwargs.pop('payOpenIdMap', None)
- if group_id == cls.groupId.default: # 平台个人中心登录
- format_group_id = cls._product_group_id(kwargs.get('productAgentId'))
- else:
- format_group_id = group_id
- user = cls.objects(openId = open_id, groupId = format_group_id).first() # type: MyUser
- if user is not None:
- need_update = False
- if 'payOpenIdMap' in kwargs:
- for key, value in kwargs['payOpenIdMap'].iteritems():
- if key in user.payOpenIdMap:
- continue
- user.set_bound_pay_openid(key = key, **(value.to_dict()))
- need_update = True
- if 'authAppId' in kwargs and kwargs['authAppId'] != user.authAppId:
- user.authAppId = kwargs['authAppId']
- need_update = True
- if 'agentId' in kwargs and kwargs['agentId'] != user.agentId:
- user.agentId = kwargs['agentId']
- need_update = True
- if 'productAgentId' in kwargs and kwargs['productAgentId'] != user.productAgentId:
- user.productAgentId = kwargs['productAgentId']
- need_update = True
- if 'avatar' in kwargs and kwargs['avatar'] != user.avatar:
- user.avatar = kwargs['avatar']
- need_update = True
- if need_update:
- user.save()
- return user
- else:
- kwargs.update({'gateway': app_platform_type})
- user = cls(openId = open_id, groupId = format_group_id, **kwargs)
- try:
- return user.save()
- except NotUniqueError:
- return cls.objects(openId = open_id, groupId = format_group_id).first()
- def group_pay(self, amount):
- # type: (VirtualCoin)->None
- """
- :param amount:
- :return:
- """
- #: 检查是否宇宙里未分配的余额和是否小于要付的钱
- valid_to_pay = bool(self.get_collection().count_documents(
- filter = {
- '_id': ObjectId(self.id),
- '$expr':
- {'$lt': [amount.mongo_amount, {'$sum': ["$universe.money.unsettled"]}]}
- }
- ))
- if valid_to_pay:
- raise NotImplementedError()
- def refund(self, payCount):
- try:
- self.update(inc__balance = payCount.amount, inc__total_consumed = -payCount.amount)
- except Exception as e:
- logger.exception(
- 'refund error = %s, openId = %s, money = %s' % (e, self.openId, payCount))
- return 0, u'退金币失败'
- return 1, ''
- def update_total_recharge(self, money):
- # type: (RMB)->int
- assert isinstance(money, RMB), 'money has to be RMB'
- updated = self.update(inc__total_recharged = money.amount)
- return updated
- def incr_balance(self, coins):
- # type: (VirtualCoin)->int
- assert isinstance(coins, VirtualCoin), 'coins had to be VirtualCoin'
- updated = self.update(inc__balance = coins.amount)
- return updated
- def recharge(self, money, bestowMoney): # type: (RMB, RMB)->MyUser
- """
- 给用户充值,同时记录累计充值数额
- :param money: 币额
- :param bestowMoney: 金额
- :return:
- """
- assert isinstance(money, RMB), 'coins had to be VirtualCoin'
- assert isinstance(bestowMoney, RMB), 'money has to be RMB'
- updated = self.update(
- inc__chargeBalance=money,
- inc__total_recharged=money,
- inc__bestowBalance=bestowMoney,
- inc__total_bestow=bestowMoney
- )
- if not updated:
- raise PostPayOrderError(u'余额和累计充值更新失败')
- return self.reload()
- @classmethod
- def deduct_balance(cls, deduct_list):
- # type:(dict)->None
- """
- 按照订单的扣款列表直接扣除用户相应balance
- :param deduct_list:
- :return:
- """
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- for deduct in deduct_list:
- query = {
- '_id': ObjectId(deduct['id'])
- }
- update = {
- '$inc': {
- 'balance': (-VirtualCoin(deduct['coins'])).mongo_amount
- }
- }
- bulker.update(query, update)
- result = bulker.execute()
- logger.debug(result['info'])
- if result['success'] == 0:
- raise ServiceException({'result': 0, 'description': u"扣款失败(1001)"})
- else:
- if len(result['info']['writeErrors']) != 0:
- raise ServiceException({'result': 0, 'description': u"扣款失败(1002)"})
- @classmethod
- def freeze_balance(cls, transaction_id, payment): # type:(str, PaymentInfo) -> bool
- """
- 按照扣款列表冻结用户相应的金额
- :param transaction_id:
- :param payment:
- :return:
- """
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- chargeBalanceField = cls.chargeBalance.name
- bestowBalanceField = cls.bestowBalance.name
- for deduct in payment.deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- 'ongoingList.transaction_id': {
- '$ne': transaction_id
- }
- }
- update = {
- '$inc': {
- chargeBalanceField: (-RMB(deduct[chargeBalanceField])).mongo_amount,
- bestowBalanceField: (-RMB(deduct[bestowBalanceField])).mongo_amount
- },
- '$addToSet': {
- 'ongoingList': {
- 'transaction_id': transaction_id,
- chargeBalanceField: deduct[chargeBalanceField],
- bestowBalanceField: deduct[bestowBalanceField]
- }
- }
- }
- bulker.update(query, update)
- result = bulker.execute()
- logger.debug(result['info'])
- if result['success'] == 0:
- raise ServiceException({'result': 0, 'description': u"扣款失败(1001)"})
- else:
- if len(result['info']['writeErrors']) != 0:
- raise ServiceException({'result': 0, 'description': u"扣款失败(1002)"})
- else:
- return True
- @classmethod
- def clear_frozen_balance(cls, transaction_id, payment, refund): # type:(str, PaymentInfo, PaymentInfo)->bool
- """
- 清除冻结金额 实际上就是相当于退费
- 如果没有退费 也需要调用一次 目的是清理掉transactionId
- """
- try:
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- chargeBalanceField = cls.chargeBalance.name
- bestowBalanceField = cls.bestowBalance.name
- # 将总消费情况 更新到第一个地址里面去
- first = True
- totalDeduct = payment.totalAmount
- totalRefund = refund.totalAmount
- for deduct in refund.deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- 'ongoingList': {
- '$elemMatch': {
- 'transaction_id': transaction_id
- }
- }
- }
- if first:
- update = {
- '$inc': {
- chargeBalanceField: deduct[chargeBalanceField],
- bestowBalanceField: deduct[bestowBalanceField],
- cls.total_consumed.name: (totalDeduct - totalRefund).mongo_amount
- },
- '$pull': {
- 'ongoingList': {
- 'transaction_id': transaction_id
- }
- }
- }
- else:
- update = {
- '$inc': {
- chargeBalanceField: deduct[chargeBalanceField],
- bestowBalanceField: deduct[bestowBalanceField],
- },
- '$pull': {
- 'ongoingList': {
- 'transaction_id': transaction_id
- }
- }
- }
- first = False
- bulker.update(query, update)
- result = bulker.execute()
- logger.debug(result['info'])
- if result['success'] == 0:
- return False
- else:
- if len(result['info']['writeErrors']) != 0:
- return False
- else:
- return True
- except Exception as e:
- logger.exception(e)
- return False
- @classmethod
- def recover_frozen_balance(cls, transaction_id, deduct_list):
- # type:(str, list)->bool
- """
- 回滚冻结的余额
- """
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- for deduct in deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- 'ongoingList': {
- '$elemMatch': {'transaction_id': transaction_id}}}
- update = {
- '$inc': {
- 'balance': deduct['coins']
- },
- '$pull': {
- 'ongoingList': {
- 'transaction_id': transaction_id, 'frozenCoins': deduct['coins']
- }}
- }
- bulker.update(query, update)
- result = bulker.execute()
- if result['success'] == 0:
- logger.error(result['info'])
- return False
- else:
- if len(result['info']['writeErrors']) != 0:
- logger.error(result['info'])
- return False
- else:
- return True
- def to_dict(self, default_avatar = settings.DEFAULT_AVATAR_URL):
- avatar = self.avatar or default_avatar or Agent.get_agent(self.agentId).get("productLogo")
- return {
- 'avatar': avatar,
- 'nickname': self.nickname,
- 'openId': self.openId,
- 'groupId': self.groupId,
- 'sex': self.sex,
- 'balance': self.balance,
- 'total_recharged': self.total_recharged,
- 'total_consumed': self.total_consumed,
- }
- @property
- def product_users(self):
- return MyUser.objects.filter(
- __raw__ = {
- 'openId': self.openId,
- '$or': [
- {
- 'agentId': self.agentId
- },
- {
- 'productAgentId': self.productAgentId
- }
- ]
- })
- @property
- def total_balance(self):
- """
- 个人中心获取balance. 需要按照agentId过滤
- :return:
- """
- totalCharge, totalBestow, total, dataList = self.filter_my_balance()
- return totalCharge + totalBestow
- def filter_my_balance(self, currentGroup=None):
- """
- 个人中心获取balance列表. 需要按照agentId过滤
- """
- dataList = []
- total = 0
- totalCharge = RMB(0)
- totalBestow = RMB(0)
- chargeBalanceField = self.__class__.chargeBalance.name
- bestowBalanceField = self.__class__.bestowBalance.name
- # dealer_partition = defaultdict(lambda: {
- # chargeBalanceField: RMB(0),
- # bestowBalanceField: RMB(0)
- # })
- users = self.product_users
- for user in users: # type: MyUser
- groupId = user.groupId
- if user.is_product_user():
- logger.debug('groupId <{}> is agent group id.'.format(groupId))
- continue
- group = Group.get_group(groupId) # type: GroupDict
- if not group:
- logger.error('no group. id = %s' % groupId)
- continue
- dealer = Dealer.get_dealer(group['ownerId']) # type: DealerDict
- if dealer is None:
- logger.error('no dealer. id = %s' % group['ownerId'])
- continue
- # dealer_partition[dealer['id']][chargeBalanceField] += user.chargeBalance
- # dealer_partition[dealer['id']][bestowBalanceField] += user.bestowBalance
- # 过滤掉为0的情况
- if user.balance == RMB(0):
- continue
- else:
- total += 1
- totalCharge += user.chargeBalance
- totalBestow += user.bestowBalance
- address = group.address
- rv = {
- 'address': address,
- 'groupId': groupId,
- 'groupName': group.groupName,
- 'dealerId': group.ownerId,
- 'balance': {
- chargeBalanceField: user.chargeBalance,
- bestowBalanceField: user.bestowBalance
- },
- 'currency': False
- }
- if currentGroup and dealer.is_currency(currentGroup, group):
- rv['currency'] = True
- dataList.append(rv)
- return totalCharge, totalBestow, total, dataList
- def get_balance_in_dealer(self, dealer, group): # type: (Dealer, GroupDict)->Tuple[RMB, RMB, RMB]
- if self.groupId != group.groupId:
- payload = self.cloneable_user_info
- payload['agentId'] = dealer.agentId
- user = MyUser.get_or_create(
- app_platform_type = self.gateway, open_id = self.openId, group_id = group.groupId, **payload)
- else:
- user = self
- users = user.product_users
- overall, dealer_balance, currency_balance = RMB(0), RMB(0), RMB(0)
- dealer_group_ids = Group.get_group_ids_of_dealer(str(dealer.id)) # type: List[GroupDict]
- groups = Group.get_groups_by_group_ids(dealer_group_ids).values()
- share_group_ids = dealer.get_currency_group_ids(group, groups)
- share_group_ids.append(group.groupId)
- for user in users:
- overall += user.balance
- if user.groupId in dealer_group_ids:
- dealer_balance += user.balance
- if user.groupId in share_group_ids:
- currency_balance += user.balance
- return overall, dealer_balance, currency_balance
- @classmethod
- def get_new_user_count(cls, groupIds, start, end):
- # type: (list, datetime.datetime, datetime.datetime)->int
- result = cls.get_collection().aggregate(
- [
- {
- '$match': {
- "groupId": {"$in": groupIds},
- "dateTimeAdded": {"$gte": start, "$lte": end}
- }
- },
- {
- '$group': {
- '_id': '$openId'
- }
- },
- {'$count': 'count'},
- ]
- )
- return next(result, {}).get("count", 0)
- @classmethod
- def get_user_count_by_filter(cls, groupIds, filterDict = {}):
- # type: (list)->int
- filter = {"groupId": {"$in": groupIds}}
- filter.update(filterDict)
- result = cls.get_collection().aggregate(
- [
- {
- '$match': filter
- },
- {
- '$group': {
- '_id': '$openId'
- }
- },
- {'$count': 'count'},
- ]
- )
- return next(result, {}).get("count", 0)
- def get_extra_info(self, key):
- return self.extra.get(key)
- def set_extra_info(self, key, value):
- extra = self.extra
- extra[key] = value
- return self.update(extra = extra)
- @staticmethod
- def get_active_info(openId, **kwargs):
- """
- 获取用户的激活状态 安骑换电用户的激活状态在一个经销商下只会有一个激活状态
- """
- users = MyUser.objects(openId = openId, **kwargs)
- for user in users:
- if user.get_extra_info("active"):
- curUser = user
- break
- else:
- return dict() # 没找到用户的激活信息
- return curUser.get_extra_info("active")
- @staticmethod
- def del_active_info(openId, **kwargs):
- users = MyUser.objects(openId = openId, **kwargs)
- for user in users:
- if user.get_extra_info("active"):
- user.set_extra_info("active", {})
- user.phoneNumber = ""
- user.save()
- break
- @staticmethod
- def set_active_info(setInfo, openId, **kwargs):
- users = MyUser.objects(openId = openId, **kwargs)
- for user in users:
- if user.get_extra_info("active"):
- curUser = user
- break
- else: # 用户第一次注册
- curUser = user
- active = curUser.get_extra_info("active")
- if not active:
- active = dict()
- active.update(setInfo)
- curUser.set_extra_info("active", active)
- if setInfo.get("isMember"): # 被经销商激活的同时,为用户添加phoneNumber字段
- curUser.phoneNumber = active.get("phoneNumber")
- curUser.save()
- return
- @property
- def cloneable_user_info(self):
- """
- 对于微信, 必须是相同的平台, 用户信息才能一致;
- 对于京东和支付宝, 用户信息都是一致的
- :return:
- """
- return {
- 'authAppId': self.authAppId,
- 'avatar': self.avatar,
- 'nickname': self.nickname,
- 'managerialAppId': self.managerialAppId,
- 'managerialOpenId': self.managerialOpenId,
- 'payOpenIdMap': self.payOpenIdMap,
- 'gateway': self.gateway,
- 'productAgentId': self.productAgentId
- }
- @property
- def my_product_user(self):
- payload = self.cloneable_user_info
- payload['agentId'] = self.productAgentId
- return MyUser.get_or_create(self.gateway, self.openId, **payload)
- @property
- def collectedDeviceList(self):
- """
- 用户收藏的设备
- :return:
- """
- return self.my_product_user.favoriteDeviceList
- @collectedDeviceList.setter
- def collectedDeviceList(self, value):
- cur_user = self.my_product_user
- cur_user.favoriteDeviceList = value
- cur_user.save()
- @property
- def phone(self):
- """
- 用户的电话号码
- 之前是存储在productUser上面 现在需要做一次转换适配
- """
- uniqueUser = UniqueUser.get_or_create(self.openId)
- # 唯一用户存储了电话的情况下 直接返回唯一用户
- if uniqueUser.phone:
- return uniqueUser.phone
- # 从以前的存储电话号码的地方找出存储的电话 如有 转移到uniquerUser上面
- phoneNumber = self.my_product_user.phoneNumber
- if phoneNumber:
- uniqueUser.update_phone(phoneNumber)
- return phoneNumber
- else:
- return phoneNumber
- @phone.setter
- def phone(self, value):
- """
- 保存用户的电话号码 将直接保存到uniqueUser上面去
- """
- uniqueUser = UniqueUser.get_or_create(self.openId)
- uniqueUser.update_phone(value)
- @property
- def user_id(self):
- """
- 用户系统分配ID
- """
- if settings.ID_SERVICE_IP:
- uniqueUser = UniqueUser.get_or_create(self.openId)
- if not uniqueUser.userId:
- try:
- user_id = IDGenService(server_ip = settings.ID_SERVICE_IP).get_id()
- uniqueUser.userId = user_id
- uniqueUser.save()
- except Exception as e:
- logger.error('update user id failure.')
- logger.exception(e)
- return uniqueUser.userId
- else:
- return None
- def record_and_check_access_day(self, name, limit):
- dayTime = datetime.datetime.now().strftime(Const.DATE_FMT)
- key = '%s-%s-%s' % (self.openId, name, dayTime)
- value = serviceCache.get(key, 0)
- if value >= limit:
- return False
- value += 1
- serviceCache.set(key, value, 24 * 60 * 60)
- return True
- @property
- def cards_num(self):
- """
- 统计该用户的该平台下的实体卡数量
- :return:
- """
- filters = {"openId": self.openId, "agentId": self.productAgentId}
- return Card.objects.filter(**filters).count()
- @property
- def many_cards(self):
- """
- 是否允许 该用户拥有多张实体卡
- :return:
- """
- if "manyCards" in self.extra and self.get_extra_info("manyCards"):
- return True
- return False
- @classmethod
- def get_dealer_ids(cls, openId, productAgentId):
- users = cls.objects(openId = openId, productAgentId = productAgentId)
- group_ids = []
- for user in users:
- if not user.is_product_user():
- group_ids.append(user.groupId)
- groups = Group.get_groups_by_group_ids(group_ids)
- dealers = set()
- for group in groups.values():
- dealers.add(group.ownerId)
- return list(dealers)
- def calc_currency_balance(self, dealer, group, groups = None):
- # type:(Dealer, GroupDict, List[GroupDict])->RMB
- share_group_ids = dealer.get_currency_group_ids(group, groups)
- share_group_ids.append(group.groupId)
- users = MyUser.objects(openId =self.openId, groupId__in=share_group_ids)
- return sum((u.balance for u in users), RMB(0))
- def calc_currency_total_recharge(self, dealer, group):
- # type:(Dealer, GroupDict)->RMB
- share_group_ids = dealer.get_currency_group_ids(group)
- share_group_ids.append(group.groupId)
- users = MyUser.objects(openId = self.openId, groupId__in = share_group_ids)
- if users.count() == 0:
- return RMB(0)
- else:
- return RMB(users.sum('total_recharged'))
- @property
- def group(self):
- if self.is_product_user():
- return None
- if hasattr(self, '__group__'):
- return getattr(self, '__group__')
- group = Group.get_group(self.groupId) # type: GroupDict
- if group:
- setattr(self, '__group__', group)
- return group
- @property
- def username(self):
- return self.nickname
- @property
- def request_limit_key(self):
- return self.openId
- @classmethod
- def get_day_used_cache(cls, openId):
- """获取用户的每日使用次数信息"""
- key = '{}_day_used'.format(openId)
- result = cls.__mgr_cache.get(key, 0)
- return result
- @classmethod
- def update_day_used_cache(cls, openId):
- """
- 更新设备的每日使用次数信息
- :param openId:
- :return:
- """
- try:
- key = '{}_day_used'.format(openId)
- set_or_incr_cache(cls.__mgr_cache, key, 1, 24 * 60 * 60)
- except Exception:
- pass
- @classmethod
- def clear_day_used_cache(cls, openId):
- """清除用户的每日使用次数信息"""
- key = '{}_day_used'.format(openId)
- cls.__mgr_cache.set(key, 0)
- @classmethod
- def get_today_can_use(cls, openId):
- usedCount = MyUser.get_day_used_cache(openId)
- if usedCount > serviceCache.get('{}_day_used_limit'.format(openId), 10):
- return False
- return True
- def prepare_refund_cash(self, refund_order, minus_total_consume): # type:(RefundMoneyRecord, VirtualCoin)->bool
- query = {
- '_id': self.id,
- 'ongoingList.transaction_id': {
- '$ne': 'r_{}'.format(str(refund_order.id))
- }
- }
- update = {
- '$inc': {
- 'chargeBalance': (-refund_order.coins).mongo_amount,
- 'total_recharged': (-refund_order.money).mongo_amount,
- },
- '$addToSet': {
- 'ongoingList': {
- 'transaction_id': 'r_{}'.format(str(refund_order.id)),
- 'chargeBalance': refund_order.money.mongo_amount,
- 'minus_total_consume': minus_total_consume.mongo_amount
- }
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def commit_refund_cash(self, refund_order):
- # type:(RefundMoneyRecord)->bool
- query = {
- '_id': self.id,
- 'ongoingList': {
- '$elemMatch': {
- 'transaction_id': 'r_{}'.format(str(refund_order.id))
- }
- }
- }
- update = {
- '$pull': {
- 'ongoingList': {'transaction_id': 'r_{}'.format(str(refund_order.id))}
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def revoke_refund_cash(self, refund_order):
- # type:(RefundMoneyRecord)->bool
- sync_key = 'r_{}'.format(str(refund_order.id))
- ongoingItem = None
- for item in self.ongoingList:
- if item['transaction_id'] == sync_key:
- ongoingItem = item
- break
- if not ongoingItem:
- return False
- query = {
- '_id': self.id,
- 'ongoingList': {
- '$elemMatch': {
- 'transaction_id': sync_key
- }
- }
- }
- update = {
- '$inc': {
- 'balance': VirtualCoin(ongoingItem['coins']).mongo_amount,
- 'total_recharged': refund_order.money.mongo_amount,
- },
- '$pull': {
- 'ongoingList': {'transaction_id': 'r_{}'.format(str(refund_order.id))}
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- @property
- def isNormal(self):
- return True
- class MyUserDetail(Searchable):
- openId = StringField(verbose_name="Myuser中的id", default="")
- dealerId = StringField(verbose_name="经销商Id", default="")
- userName = StringField(verbose_name="用户姓名", default="")
- userPhone = StringField(verbose_name="用户电话", default="")
- userUnit = StringField(verbose_name="用户地址单元", default="")
- userFloor = StringField(verbose_name="用户地址楼层", default="")
- userRoom = StringField(verbose_name="用户地址房间", default="")
- meta = {
- "collection": "MyUserDetail",
- "db_alias": "logdata"}
- @classmethod
- def get_collection(cls):
- # type: ()->Collection
- return cls._get_collection()
- class RechargeRecordDict(dict):
- def __repr__(self):
- return '<RechargeRecordDict id=%s>' % (self.get('id'),)
- @property
- def v(self):
- return dict(self)
- @property
- def ownerId(self):
- return self.get('ownerId')
- @property
- def groupId(self):
- return self.get('groupId')
- @property
- def money(self):
- return RMB(self['money'])
- @property
- def coins(self):
- return VirtualCoin(self['coins'])
- @property
- def attachParas(self):
- return self.get('attachParas')
- @property
- def extraInfo(self):
- return self.get('extraInfo')
- @property
- def group(self):
- # type:()->GroupDict
- _attr_name = '__my_group__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- group = Group.get_group(self.groupId)
- setattr(self, _attr_name, group)
- return group
- @property
- def owner(self):
- _attr_name = '__my_owner__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- dealer = Dealer.objects(id = self.ownerId).first()
- setattr(self, _attr_name, dealer)
- return dealer
- @property
- def my_gateway(self):
- return self.get('gateway')
- @property
- def pay_app_type(self):
- return self.get('payAppType')
- @property
- def pay_gateway_key(self):
- return self.get('payGatewayKey')
- @property
- def withdraw_source_key(self):
- return self.get('withdrawSourceKey')
- @property
- def dev_type_name(self):
- if 'devTypeName' in self:
- return self.get('devTypeName')
- elif 'devType' in self:
- return self.get('devType')
- else:
- return u'其他'
- @property
- def logicalCode(self):
- return self.get('logicalCode')
- @property
- def address(self):
- return self.get('address')
- @property
- def via(self):
- return self.get('via')
- @property
- def partition_map(self):
- """
- 新订单模型下, 创建订单就会生成partitionMap. 所以直接取
- :return:
- """
- return self.extraInfo.get('partitionMap', None)
- def calc_income_partitions(self, partition_map):
- totalMoney = self.money
- leftMoney = totalMoney
- agent_partitions = partition_map.get(PARTITION_ROLE.AGENT, list())
- if agent_partitions:
- for agentShare in agent_partitions:
- shareMoney = totalMoney * Percent(agentShare['share']).as_ratio
- if shareMoney > leftMoney:
- shareMoney = leftMoney
- leftMoney = leftMoney - shareMoney
- agentShare['money'] = shareMoney.mongo_amount
- partner_partitions = partition_map.get(PARTITION_ROLE.PARTNER, list())
- if partner_partitions:
- for partnerShare in partner_partitions:
- shareMoney = totalMoney * Percent(partnerShare['share']).as_ratio
- if shareMoney > leftMoney:
- shareMoney = leftMoney
- leftMoney = leftMoney - shareMoney
- partnerShare['money'] = shareMoney.mongo_amount
- owner_partitions = partition_map.get(PARTITION_ROLE.OWNER, list())
- assert len(owner_partitions) == 1, u'经销商只能有一个'
- ownerShare = owner_partitions[0]
- ownerShare['money'] = leftMoney.mongo_amount
- return partition_map
- def calc_refund_partitions(self, pay_split_map):
- totalMoney = self.money
- leftMoney = totalMoney
- share_key = lambda _role, _id: '{}_{}'.format(_role, _id)
- old_shares_map = {}
- partition_map = {
- PARTITION_ROLE.AGENT: [],
- PARTITION_ROLE.PARTNER: [],
- PARTITION_ROLE.OWNER: []
- }
- agent_partitions = pay_split_map.get(PARTITION_ROLE.AGENT, list())
- if agent_partitions:
- for agentShare in agent_partitions:
- old_shares_map[share_key(agentShare['role'], agentShare['id'])] = RMB(agentShare['money'])
- newAgentShare = copy.deepcopy(agentShare)
- shareMoney = totalMoney * Percent(agentShare['share']).as_ratio
- if abs(shareMoney) > abs(leftMoney):
- shareMoney = leftMoney
- leftMoney = leftMoney - shareMoney
- newAgentShare['money'] = shareMoney.mongo_amount
- partition_map[PARTITION_ROLE.AGENT].append(newAgentShare)
- partner_partitions = pay_split_map.get(PARTITION_ROLE.PARTNER, list())
- if partner_partitions:
- for partnerShare in partner_partitions:
- old_shares_map[share_key(partnerShare['role'], partnerShare['id'])] = RMB(partnerShare['money'])
- newPartnerShare = copy.deepcopy(partnerShare)
- shareMoney = totalMoney * Percent(partnerShare['share']).as_ratio
- if abs(shareMoney) > abs(leftMoney):
- shareMoney = leftMoney
- leftMoney = leftMoney - shareMoney
- newPartnerShare['money'] = shareMoney.mongo_amount
- partition_map[PARTITION_ROLE.PARTNER].append(newPartnerShare)
- owner_partitions = pay_split_map.get(PARTITION_ROLE.OWNER, list())
- assert len(owner_partitions) == 1, u'经销商只能有一个'
- ownerShare = owner_partitions[0]
- newOwnerShare = copy.deepcopy(ownerShare)
- old_share_money = RMB(ownerShare['money'])
- if abs(leftMoney) <= old_share_money:
- newOwnerShare['money'] = leftMoney.mongo_amount
- else:
- diff = abs(leftMoney) - old_share_money
- shares = list(flatten(partition_map.values()))
- shares.sort(key = lambda x: Percent(x['share']), reverse = True)
- for share in shares:
- if RMB(share['money']) - RMB('0.01') + RMB(
- old_shares_map[share_key(share['role'], share['id'])]) >= RMB(0):
- share['money'] = (RMB(share['money']) - RMB('0.01')).mongo_amount
- diff -= RMB('0.01')
- leftMoney += RMB('0.01')
- if diff <= RMB(0):
- break
- assert diff == RMB(0), u'分账金额错误'
- newOwnerShare['money'] = leftMoney.mongo_amount
- partition_map[PARTITION_ROLE.OWNER] = [newOwnerShare]
- for role, items in partition_map.iteritems():
- for item in items:
- if RMB(item['money']) == RMB(0):
- item['money'] = RMB(0).mongo_amount
- return partition_map
- class RechargeRecord(OrderRecordBase):
- """
- 三个作用:
- 1、经销商收益记录(特殊情况包括平台的收益记录,例如1毛保险单情况下)
- 2、充值订单,模型增加refundInfo信息
- 3、商家派送(金币,红包等)订单
- """
- class PayResult(IterConstant):
- UNPAY = 'unPay'
- SUCCESS = 'success'
- FAILED = 'failed'
- CANCEL = 'cancel'
- REFUNDING = 'refunding' # 退费处理中
- CLOSE = 'close' # 退款订单用, 被退单
- orderNo = StringField(verbose_name = u"订单号", unique = True)
- wxOrderNo = StringField(verbose_name = u"渠道订单号. 聚合商户,是聚合平台商户订单号;直连商户则是微信或支付宝订单号。互联互通订单号也用这个")
- transactionId = StringField(verbose_name = u"微信或者支付宝订单号", default = None)
- ownerId = StringField(verbose_name = u"设备的owner", default = "")
- #: 充值的金钱数额 incr(user.balance)
- money = MonetaryField(verbose_name = u"充值", default = RMB('0.00'))
- #: 用户购得的金币,如果是卡,就是充值的钱
- coins = VirtualCoinField(verbose_name = u"金币数目", default = VirtualCoin('0.00'))
- subject = StringField(verbose_name = u"订单产品或者服务描述", default = "")
- result = StringField(verbose_name = u"充值或者服务结果", default = PayResult.UNPAY)
- extraInfo = DictField(verbose_name = u"支付订单模型信息", default = {})
- description = StringField(verbose_name = u"结果描述,一般为第三方错误码", default = None)
- # 以下字段为充值订单的模型字段, MODEL中不在体现
- #: 当用户余额为0,点击投币,生成充值订单,用户完成后直接自动投相应币数
- #: 此举为优化用户体验,大多数情况用户对充值并不优先选择,使其能够缩短支付使用流程,用完即走
- isQuickPay = BooleanField(verbose_name = u"是否直接支付使用,默认为否", default = False)
- selectedQuickPayPackageId = StringField(verbose_name = u"快捷支付所选的投币套餐", default = None)
- via = StringField(verbose_name = u"充值途径 =: (recharge|sendcoin|refund|chargeCard|swap)", default = 'recharge')
- finishedTime = DateTimeField(default = None, verbose_name = u"支付完成时间")
- # 派币需要记录信息
- operator = StringField(verbose_name = u"操作员", default = None)
- attachParas = DictField(verbose_name = u"消费订单信息", default = {})
- gateway = StringField(verbose_name = u'支付网关类型', default = '')
- payAppType = StringField(verbose_name = u'支付应用类型', default = None)
- payGatewayKey = StringField(verbose_name = u'支付网关key', default = None)
- withdrawSourceKey = StringField(verbose_name = u'提现商户平台标识,资金池模式下和代理商相同', default = None)
- isAllocatedCardMoney = BooleanField(verbose_name = u'是否分钱了', default = None)
- search_fields = ('orderNo', 'wxOrderNo')
- _shard_key = ('ownerId', 'dateTimeAdded')
- _origin_meta = {
- "collection": "RechargeRecord",
- "db_alias": "default"
- }
- meta = _origin_meta
- def __repr__(self):
- return '<RechargeRecord id=%s, orderNo=%s, wxOrderNo=%s, via=%s>' % (str(self.id), self.orderNo, self.wxOrderNo, self.via)
- @cached_property
- def payGateway(self):
- return PaymentGateway.clone_from_order(self)
- @cached_property
- def user(self):
- return MyUser.objects.filter(openId=self.openId, groupId=self.groupId).first()
- @cached_property
- def dealer(self):
- return Dealer.objects(id=self.ownerId).get()
- @property
- def goods(self): # type:()->dict
- return {}
- @property
- def timeout(self): # type:()-> bool
- """支付超时"""
- return (datetime.datetime.now() - self.dateTimeAdded).total_seconds() > 60 * 3
- @property
- def chargeAmount(self):
- return self.money
- @property
- def bestowAmount(self):
- return RMB(self.coins) - RMB(self.money)
- @property
- def my_gateway(self):
- return self.gateway
- @property
- def pay_app_type(self):
- return self.payAppType
- @property
- def pay_gateway_key(self):
- return self.payGatewayKey
- @property
- def owner(self):
- return self.dealer
- @property
- def payOrder(self):
- """
- 用户的实际支付的订单 由于业务需要 可能被拆分为多笔
- # TODO 需要搞定历史数据库的问题
- """
- if self.isSubOrder:
- _id = self.attachParas.get("tradeOrderId", None) or self.extraInfo.get('tradeOrderId', None)
- return self.__class__.objects.get(id = _id)
- return self
- @property
- def isSubOrder(self):
- return "tradeOrderId" in self.extraInfo or "tradeOrderId" in self.attachParas
- @property
- def card_no(self):
- return self.attachParas.get('cardNo', None)
- @property
- def myuser(self):
- _attr_name = '__my_user__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- _myuser = MyUser.get_or_create(
- open_id = self.openId,
- group_id = self.groupId,
- app_platform_type = self.gateway) # type: MyUser
- setattr(self, _attr_name, _myuser)
- return _myuser
- @property
- def my_subject(self):
- if self.subject:
- return self.subject
- else:
- return RECHARGE_RECORD_VIA_TRANSLATION.get(self.via)
- @property
- def my_description(self):
- if self.description:
- return self.description
- else:
- return getattr(self, 'desc', '')
- @property
- def my_via(self):
- """
- 老的订单模型, 对于recharge, 根据是否快速支付做一个调整
- :return:
- """
- return self.via
- @property
- def withdraw_source_key(self):
- return self.withdrawSourceKey
- @property
- def partition_map(self):
- return self.to_dict_obj.partition_map
- @property
- def fen_total_fee(self):
- return int(self.money * 100)
- @property
- def amount(self):
- # type:()->RMB
- return self.money
- @property
- def my_amount(self):
- if self.via in [RechargeRecordVia.RefundCash,
- RechargeRecordVia.Cash,
- RechargeRecordVia.VirtualCard,
- RechargeRecordVia.MonthlyPackage]:
- return '{}元'.format(abs(self.money))
- else:
- return self.coins
- @property
- def mongo_amount(self):
- return self.amount.mongo_amount
- @property
- def extra_detail_info(self):
- rv = {}
- # TODO: 需要根据RechargeRecord建立各VIA下的订单详细信息
- if self.via == 'chargeCard':
- cardId = str(self.attachParas.get('cardId'))
- card = Card.objects(id = cardId).first()
- if card:
- rv.update({'cardNo': card.cardNo, 'cardType': card.cardType})
- elif self.via == 'chargeVirtualCard':
- if 'type' not in self.attachParas:
- # 老模型在new的情况下, 是没有type字段的
- cardNo = self.attachParas.get('cardNo', None)
- if cardNo:
- rv.update({'cardNo': cardNo})
- cardId = self.attachParas.get('cardId', None)
- if cardId:
- cardTmpl = VirtualCard.objects(id = cardId).first() # type: VirtualCard
- if cardTmpl:
- rv.update({'cardName': cardTmpl.cardName})
- else:
- if 'cardId' in self.attachParas:
- virtualCard = UserVirtualCard.objects(
- id = self.attachParas['cardId']).first() # type: UserVirtualCard
- rv.update({
- 'cardNo': virtualCard.cardNo, 'cardName': virtualCard.cardName
- })
- return rv
- @property
- def summary_info(self):
- rv = {
- 'id': str(self.id),
- 'createdTime': self.to_datetime_str(self.dateTimeAdded),
- 'money': self.money,
- 'coins': self.coins,
- 'groupName': self.groupName,
- 'groupNumber': self.groupNumber,
- 'address': self.address,
- 'logicalCode': self.logicalCode,
- 'devTypeName': self.dev_type_name,
- 'devTypeCode': self.dev_type_code,
- 'ownerId': self.ownerId,
- 'via': self.via
- }
- if self.via == 'chargeVirtualCard':
- rv.pop('coins')
- return rv
- @property
- def created_date(self):
- return self.dateTimeAdded
- @property
- def finished_time(self):
- if self.result != self.PayResult.SUCCESS:
- return ''
- if self.finishedTime:
- return self.to_datetime_str(self.finishedTime)
- return self.to_datetime_str(self.dateTimeAdded)
- @property
- def ledger_enable(self):
- return self.result in [self.PayResult.SUCCESS, self.PayResult.REFUNDING]
- @property
- def is_ledgered(self):
- """
- 判断该订单是否已经 分账获取了收益
- :return:
- """
- if self.result != self.PayResult.SUCCESS:
- # 失败订单肯定是没有分账的
- return False
- if self.isAllocatedCardMoney:
- return True
- # 没有该字段的订单 直接进行一次检查 看看有没有分账
- from apps.web.common.proxy import ClientDealerIncomeModelProxy
- try:
- ledgerRecord = ClientDealerIncomeModelProxy.get_one(ref_id = self.id)
- except Exception as e:
- logger.exception(e)
- return False
- return bool(ledgerRecord)
- @property
- def notLedgerDesc(self):
- """
- 没有分账的描述值 12小时 72小时分界线
- :return:
- """
- # 未超过 12 小时的情况 可以当成是订单尚未结束
- if self.dateTimeAdded + datetime.timedelta(hours = 12) > datetime.datetime.now():
- desc = u"订单尚未获取收益,可能设备正在运行"
- # 超过 12 小时但是 没有到 72 小时的情况 告知订单可能可能出现了某种问题 等待系统监测自动结账
- elif self.dateTimeAdded + datetime.timedelta(hours = 72) > datetime.datetime.now():
- desc = u"订单尚未获取收益,可能由于网络原因造成订单未关闭,系统将于{}小时后强制分账,请勿担心!".format(
- 72 - (datetime.datetime.now().hour - self.dateTimeAdded.hour))
- # 超过72小时还没有分账的 问题单
- else:
- desc = u"订单尚未获取收益,请联系平台运营人员进行处理"
- return desc
- @property
- def used_port(self):
- if self.attachParas and 'chargeIndex' in self.attachParas and self.attachParas['chargeIndex']:
- return str(self.attachParas['chargeIndex'])
- return ''
- @property
- def startKey(self):
- return self.attachParas.get('startKey', None)
- @property
- def refundOrders(self):
- return RefundMoneyRecord.objects.filter(rechargeObjId = self.id)
- @property
- def has_refund_order(self):
- if self.refundOrders:
- return True
- else:
- return False
- @property
- def is_temp_package(self):
- return 'isTempPackage' in self.attachParas and self.attachParas['isTempPackage'] is True
- @property
- def to_dict_obj(self):
- return RechargeRecordDict({
- 'via': self.via,
- 'money': self.money,
- 'coins': self.coins,
- 'attachParas': self.attachParas,
- 'extraInfo': self.extraInfo,
- 'gateway': self.my_gateway,
- 'payAppType': self.pay_app_type,
- 'payGatewayKey': self.pay_gateway_key
- })
- @classmethod
- def get_record(cls, order_no, dealer_id = None):
- # type: (str, str)->Optional[RechargeRecord]
- # TODO 数据库分片修改
- # record = RechargeRecord.objects(ownerId = dealer_id, orderNo = order_no).first()
- record = cls.objects(orderNo = order_no).first()
- return record
- @classmethod
- def calc_count(cls, filter, group = None, limit = None, sort = None, allowDiskUse = False):
- # type: (dict, dict, int, dict, bool)->int
- pipeline = []
- if filter:
- pipeline.append({
- '$match': filter
- })
- if group:
- pipeline.append({
- '$group': group
- })
- if limit:
- pipeline.append({
- '$limit': limit
- })
- if sort:
- pipeline.append({
- '$sort': sort
- })
- pipeline.append({'$count': 'count'})
- result = cls.get_collection().aggregate(pipeline, allowDiskUse = allowDiskUse)
- return next(result, {}).get('count', 0)
- @classmethod
- def get_recharged_records(cls, **kwargs):
- return cls.objects(via = 'recharge', result = cls.PayResult.SUCCESS, **kwargs).order_by('-dateTimeAdded')
- @classmethod
- def get_card_recharged_records(cls, **kwargs):
- return cls.objects(via = 'chargeCard', result = cls.PayResult.SUCCESS, **kwargs).order_by('-dateTimeAdded')
- @classmethod
- def issue_pay_order(cls, context, gateway, **payload):
- payload.update({
- "openId": context.user.openId,
- "nickname": context.user.nickname,
- 'gateway': gateway.gateway_type,
- 'payAppType': gateway.pay_app_type,
- 'payGatewayKey': gateway.gateway_key,
- 'withdrawSourceKey': gateway.withdraw_source_key()
- })
- if gateway.pay_app_type in [PayAppType.WECHAT, PayAppType.ALIPAY]:
- payload['extraInfo'].update({
- 'payOpenId': context.user.get_bound_pay_openid(gateway.bound_openid_key)
- })
- if "result" not in payload:
- payload["result"] = cls.PayResult.UNPAY
- order = cls(payload)
- return order.save()
- @classmethod
- def issue_from_auto_sim_order(cls, owner, sim_recharge, device, group):
- # type:(Dealer, DealerRechargeRecord, Device, GroupDict)->RechargeRecord
- payload = {
- 'openId': owner.username,
- 'nickname': owner.nickname,
- 'orderNo': sim_recharge.orderNo,
- 'ownerId': sim_recharge.dealerId,
- 'money': -RMB(sim_recharge.totalFee/100),
- 'coins': VirtualCoin(0),
- 'subject': sim_recharge.subject,
- 'result': cls.PayResult.SUCCESS,
- 'via': RechargeRecordVia.AutoSim,
- 'finishedTime': sim_recharge.finishedTime,
- 'gateway': sim_recharge.my_gateway,
- 'payAppType': sim_recharge.pay_app_type,
- 'payGatewayKey': sim_recharge.pay_gateway_key,
- 'isAllocatedCardMoney': True,
- 'devNo': device.devNo,
- 'devType': device.devTypeName,
- 'devTypeName': device.devTypeName,
- 'devTypeCode': device.devTypeCode,
- 'logicalCode': device.logicalCode,
- 'groupId': device.groupId,
- 'address': group.address,
- 'groupNumber': device.groupNumber,
- 'groupName': group.groupName
- }
- record = cls(**payload).save()
- record.group = group
- return record
- @classmethod
- def viaText(cls, via, quickPay):
- if via == RechargeRecordVia.Balance:
- if quickPay:
- return u'快捷支付'
- else:
- return u'优惠充值'
- else:
- return RECHARGE_RECORD_VIA_TRANSLATION[via]
- @classmethod
- def gatewayText(cls, gateway):
- return APP_PLATFORM_TYPE_TRANSLATION.get(gateway, u'未知')
- @classmethod
- def from_feedback(cls, feedback, refundCoins):
- # type: (FeedBack, VirtualCoin)->RechargeRecord
- return cls(
- orderNo = str(uuid.uuid4()),
- coins = refundCoins,
- money = RMB(0),
- openId = feedback.openId,
- groupId = feedback.groupId,
- devNo = feedback.devNo,
- ownerId = feedback.ownerId,
- groupName = feedback.groupName,
- groupNumber = feedback.groupNumber,
- address = feedback.address,
- wxOrderNo = u'老板退币',
- devTypeName = feedback.devTypeName,
- nickname = feedback.nickname,
- result = cls.PayResult.SUCCESS,
- via = 'refund')
- @classmethod
- def issue_pay_record(cls, context, **payload):
- # type: (OrderBuilderContext, Dict)->RechargeRecord
- user = context.user
- payment_gateway = context.pay_gateway # type: PaymentGateway
- payload.update({
- 'openId': user.openId,
- 'nickname': user.nickname,
- 'gateway': payment_gateway.gateway_type,
- 'payAppType': payment_gateway.pay_app_type,
- 'payGatewayKey': payment_gateway.gateway_key,
- 'withdrawSourceKey': payment_gateway.withdraw_source_key()
- })
- if 'attachParas' not in payload:
- payload['attachParas'] = {}
- if 'extraInfo' not in payload:
- payload['extraInfo'] = {}
- if payment_gateway.pay_app_type in [PayAppType.WECHAT, PayAppType.ALIPAY]:
- payload['extraInfo'].update({
- 'payOpenId': user.get_bound_pay_openid(payment_gateway.bound_openid_key)
- })
- if "result" not in payload:
- payload['result'] = cls.PayResult.UNPAY
- record = cls(**payload) # type: RechargeRecord
- return record.save()
- def to_dict(self, json_safe=False):
- money = str(self.money) if json_safe else self.money
- return {
- 'ownerId': self.ownerId,
- 'nickname': self.nickname,
- 'money': money,
- 'gateway': self.my_gateway,
- 'via': self.via,
- }
- def to_json_dict(self):
- return self.to_dict(json_safe = True)
-
- @classmethod
- def issue_refund_order(cls, dealer_recharge_record, refundFee, sub_type, via): # type:(DealerRechargeRecord, RMB, str, via)->RechargeRecord
- """
- 在经销商使用钱包余额支付情况下, 给经销商建立负收益记录
- :param dealer_recharge_record:
- :param refundFee:
- :param sub_type:
- :param via:
- :return:
- """
- from apps.web.common.proxy import ClientRechargeModelProxy
- record = ClientRechargeModelProxy.get_one(
- shard_filter = {'ownerId': dealer_recharge_record.ownerId},
- orderNo = dealer_recharge_record.orderNo) # type: RechargeRecord
- if not record:
- owner = Dealer.objects(id = dealer_recharge_record.ownerId).first()
- device = Device.get_dev(dealer_recharge_record.items[0]['devNo']) # type: DeviceDict
- if device.ownerId != dealer_recharge_record.ownerId:
- raise UserServerException(u'该设备所属经销商和订单不一致,无法退款')
- record = RechargeRecord(
- openId = owner.username,
- nickname = owner.nickname,
- orderNo = dealer_recharge_record.orderNo,
- ownerId = dealer_recharge_record.ownerId,
- subject = dealer_recharge_record.subject,
- finishedTime = dealer_recharge_record.finishedTime,
- gateway = dealer_recharge_record.gateway,
- payAppType = dealer_recharge_record.payAppType,
- payGatewayKey = dealer_recharge_record.payGatewayKey,
- devNo = device.devNo,
- devType = device.devTypeName,
- devTypeName = device.devTypeName,
- devTypeCode = device.devTypeCode,
- logicalCode = device.logicalCode,
- groupId = device.groupId,
- address = device.group.address,
- groupNumber = device.groupNumber,
- groupName = device.group.groupName)
- payload = {
- 'openId': record.openId,
- 'nickname': record.nickname,
- 'orderNo': OrderNoMaker.make_order_no_32(
- identifier = record.logicalCode,
- main_type = OrderMainType.PAY,
- sub_type = sub_type),
- 'ownerId': record.ownerId,
- 'money': refundFee,
- 'coins': VirtualCoin(0),
- 'subject': '{} 退费'.format(record.subject),
- 'result': cls.PayResult.SUCCESS,
- 'via': via,
- 'finishedTime': datetime.datetime.now(),
- 'gateway': record.my_gateway,
- 'payAppType': record.pay_app_type,
- 'payGatewayKey': record.pay_gateway_key,
- 'isAllocatedCardMoney': True,
- 'devNo': record.devNo,
- 'devType': record.devTypeName,
- 'devTypeName': record.devTypeName,
- 'devTypeCode': record.devTypeCode,
- 'logicalCode': record.logicalCode,
- 'groupId': record.groupId,
- 'address': record.address,
- 'groupNumber': record.groupNumber,
- 'groupName': record.groupName
- }
- record = cls(**payload).save()
- return record
- def succeed(self, **kwargs):
- payload = {
- 'result': self.PayResult.SUCCESS
- }
- if kwargs:
- payload.update(kwargs)
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'result': {'$nin': [self.PayResult.SUCCESS, self.PayResult.CLOSE]}},
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def close(self, **kwargs):
- payload = {
- 'result': self.PayResult.CLOSE
- }
- if kwargs:
- payload.update(kwargs)
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'result': {'$nin': [self.PayResult.SUCCESS, self.PayResult.CLOSE]}},
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def fail(self, **kwargs):
- self.update(result = self.PayResult.FAILED, **kwargs)
- return self.reload()
- def cancel(self):
- payload = {
- 'result': self.PayResult.CANCEL,
- 'finishedTime': datetime.datetime.now()
- }
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id), 'result': self.PayResult.UNPAY},
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- @property
- def is_success(self):
- return self.result == self.PayResult.SUCCESS
- @property
- def is_refunding(self):
- return self.result == self.PayResult.REFUNDING
- @property
- def is_fail(self):
- return self.result == self.PayResult.FAILED
- @property
- def is_cancel(self):
- return self.result == self.PayResult.CANCEL
- @property
- def is_close(self):
- return self.result == self.PayResult.CLOSE
- def to_detail(self):
- # type: ()->dict
- """
- :return:
- """
- return {
- 'id': self.id,
- 'rechargeTradeNo': self.orderNo,
- 'payResult': self.result,
- 'ownerId': self.ownerId,
- 'gateway': self.my_gateway,
- 'openId': self.openId,
- 'groupId': self.groupId,
- 'userNickname': self.nickname,
- 'orderAmount': self.money,
- 'coins': self.coins,
- 'devNo': self.devNo,
- 'logicalCode': self.logicalCode,
- 'devTypeName': self.dev_type_name,
- 'groupName': self.groupName,
- 'groupNumber': self.groupNumber,
- 'address': self.address,
- 'createdTime': self.dateTimeAdded,
- 'completionTime': self.finished_time,
- 'via': self.via,
- 'isQuickPay': self.isQuickPay,
- 'startKey': self.attachParas.get('startKey', None),
- # 以下字段和以前做兼容
- 'outTradeNo': self.orderNo,
- 'finishedTime': self.finished_time,
- 'gatewayTradeNo': self.wxOrderNo,
- 'result': self.result
- }
- @classmethod
- def have_illegal_order(cls, dealerId, maxPrice = RMB('50.00')):
- return False
- def to_dict_for_super_admin(self):
- from apps.web.common.proxy import ClientConsumeModelProxy
- orderResult = ''
- isNormalDesc = ''
- via = ''
- if self.result == 'unPay':
- orderResult = u'未付款'
- elif self.result == 'failed':
- orderResult = u'充值失败'
- elif self.result == 'success':
- orderResult = u'充值成功'
- if self.via == '':
- via = u'充值金币'
- elif self.via == '':
- via = u'充值卡'
- if 'consumeRecordId' in self.attachParas:
- # 部分后支付的设备类型, 在支付的时候会塞入消费单的id
- consumeRcd = ClientConsumeModelProxy.get_one(
- shard_filter = {'ownerId': self.ownerId},
- id = self.attachParas.get("consumeRecordId")) # type: ConsumeRecord
- elif 'startKey' in self.attachParas and self.attachParas['startKey']:
- consumeRcd = ClientConsumeModelProxy.get_one(
- shard_filter = {'ownerId': self.ownerId},
- foreign_id = str(self.id),
- startKey = self.attachParas['startKey']) # type: ConsumeRecord
- else:
- consumeRcd = None
- if consumeRcd is not None:
- if consumeRcd.isNormal is True:
- isNormalDesc = u'消费成功'
- else:
- isNormalDesc = u'消费失败'
- return {
- 'wxOrderNo': self.wxOrderNo,
- 'orderNo': self.orderNo,
- 'orderResult': orderResult,
- 'rechargeUser': self.nickname + ' ' + self.openId,
- 'rechargeType': via,
- 'money': str(self.money),
- 'coins': str(self.coins),
- 'orderDetail': self.attachParas,
- 'orderCreateTime': self.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S'),
- 'logicalCode': self.logicalCode,
- 'devNo': self.devNo,
- 'devTypeCode': self.dev_type_code,
- 'consumeOrderNo': consumeRcd.orderNo if consumeRcd else '',
- 'consumeUser': consumeRcd.nickname + ' ' + consumeRcd.openId if consumeRcd else '',
- 'consumeCoins': str(consumeRcd.coin) if consumeRcd else '',
- 'consumeResult': isNormalDesc if consumeRcd else '',
- 'failedReason': consumeRcd.errorDesc if consumeRcd else '',
- 'consumeOrderCreateTime': consumeRcd.dateTimeAdded.strftime('%Y-%m-%d %H:%M:%S') if consumeRcd else '',
- 'consumeDetail': consumeRcd.attachParas if consumeRcd else {},
- 'consumeDict': consumeRcd.servicedInfo if consumeRcd else {}
- }
- def set_ledgered(self):
- try:
- modified = self.update(isAllocatedCardMoney = True)
- if not modified:
- logger.error('failed to update isAllocatedCardMoney for record=%r' % (self,))
- except Exception as e:
- logger.exception(e)
- def new_refund_cash_order(self, refund_order): # type: (RefundMoneyRecord)->RechargeRecord
- payload = {}
- for field in self.__class__._fields_ordered:
- if field in ['id', 'orderNo', 'wxOrderNo', 'transactionId', 'dateTimeAdded', 'ledgerStatus',
- 'finishedTime', 'notifiedTime', 'operator', 'devTypeName', 'money', 'coins',
- 'selectedQuickPayPackageId', 'devType', 'attachParas', 'isAllocatedCardMoney', 'subject']:
- continue
- payload.update({
- field: getattr(self.payOrder, field)
- })
- payload.update({
- 'orderNo': refund_order.orderNo,
- 'money': -refund_order.money,
- 'coins': -refund_order.coins,
- 'devTypeName': self.dev_type_name,
- 'via': RechargeRecordVia.RefundCash,
- 'isAllocatedCardMoney': False,
- 'gateway': self.my_gateway,
- 'payGatewayKey': self.pay_gateway_key,
- 'payAppType': self.pay_app_type,
- 'withdrawSourceKey': self.withdraw_source_key,
- 'result': self.PayResult.REFUNDING,
- 'extraInfo': {
- 'refPay': {'objId': str(self.id)}
- },
- 'subject': '{} {}'.format(self.subject, u'退费')
- })
- refund_order_record = self.__class__(**payload).save() # type: RechargeRecord
- refund_order.refund_order_record = refund_order_record
- self.update(push__extraInfo__refRefund = {
- 'objId': str(refund_order_record.id),
- 'money': refund_order.money.mongo_amount,
- 'coins': refund_order.coins.mongo_amount
- })
- return refund_order_record
- def is_refund_available(self, customer): # type:(CapitalUser) -> bool
- if customer.role == ROLE.dealer:
- if self.ownerId != str(customer.id):
- logger.warning('is not my order. {} != {}'.format(self.ownerId, str(customer.id)))
- return False
- if self.via not in [USER_RECHARGE_TYPE.RECHARGE, USER_RECHARGE_TYPE.RECHARGE_CASH]:
- return False
- return True
- return False
- class Dispute(EmbeddedDocument):
- feedback_id = ObjectIdField()
- class CardRechargeOrder(Searchable):
- """
- 卡充值订单
- """
- orderNo = StringField(verbose_name=u"订单号", default="")
- cardId = StringField(verbose_name=u"实体卡ID", default="")
- cardNo = StringField(verbose_name=u"实体卡NO", default="")
- # 卡绑定的发行地址
- groupId = StringField(verbose_name=u"设备地址编号", default="")
- address = StringField(verbose_name=u"设备地址", default="")
- groupName = StringField(verbose_name=u"交易场地", default="")
- openId = StringField(verbose_name=u"openId", default="")
- money = MonetaryField(verbose_name=u"付款金额", default=RMB('0.00'))
- coins = MonetaryField(verbose_name=u"充值金额", default=RMB('0.00'))
- remarks = StringField(verbose_name=u"备注", default="")
- dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'生成时间')
- sequanceNo = StringField(verbose_name=u"交易流水号", default="")
- status = StringField(verbose_name=u"状态", default="")
- rechargeType = StringField(verbose_name=u"充值类型,一般分网络充值、线下设备充值", default="") # netpay/device
- dealerId = StringField(verbose_name=u'订单ownerId, 集群需要', default=None)
- rechargeNo = ObjectIdField(verbose_name=u"订单ID")
- operationLog = ListField(verbose_name=u'历史处理结果', default=[])
- processingLog = DictField(verbose_name=u'')
- meta = {
- "collection": "cardRechargeOrder",
- "db_alias": "default",
- }
- def __repr__(self):
- return '<CardRechargeOrder(id=%s, orderNo=%s, cardNo=%s)>' % (str(self.id), self.orderNo, self.cardNo)
- @cached_property
- def rechargeOrder(self):
- return RechargeRecord.objects.filter(id=self.rechargeNo).first()
- @property
- def chargeAmount(self):
- return self.money
- @property
- def bestowAmount(self):
- return RMB(self.coins) - RMB(self.money)
- # 获取卡的订单,但是没有支付的
- @staticmethod
- def get_last_to_do_one(cardId):
- obj = CardRechargeOrder.objects(
- cardId=cardId, status='finishedPay',
- rechargeType__in=["netpay", "sendCoin"]
- ).order_by('-dateTimeAdded').first()
- return obj
- # 获取卡的订单,但是没有支付的
- @staticmethod
- def get_to_do_list(cardId):
- objs = CardRechargeOrder.objects.filter(
- cardId=cardId, status='finishedPay',
- rechargeType__in=['netpay', "sendCoin"]
- )
- money, coins = RMB(0), RMB(0)
- orderNos = []
- cardOrderNos = []
- for obj in objs:
- money += obj.money
- coins += obj.coins
- orderNos.append(obj.rechargeNo)
- cardOrderNos.append(obj.orderNo)
- return money, coins, orderNos, cardOrderNos
- # 更新卡的为到账订单,更新为已经支付
- @staticmethod
- def update_card_order_has_finished(cardId):
- CardRechargeOrder.get_collection().update(
- {'cardId': cardId, 'status': 'finishedPay', 'rechargeType': {"$in": ["netpay", "sendCoin"]}},
- {'$set': {'status': 'finished'}},
- multi = True
- )
- def init_processing_log(self, device, sendMoney, preBalance):
- # type: (DeviceDict, RMB, RMB)->CardRechargeOrder
- self.processingLog = {
- 'devNo': device.devNo,
- 'logicalCode': device.logicalCode,
- 'sendMoney': sendMoney.mongo_amount,
- 'preBalance': preBalance.mongo_amount,
- 'code': device.get('devType', {}).get('code', ''),
- 'time': datetime.datetime.now()
- }
- # return self.save()
- def update_after_recharge_ic_card(self, device, sendMoney, preBalance,
- result = ErrorCode.SUCCESS, description = '', syncBalance = None):
- # type: (DeviceDict, RMB, RMB, int, basestring, Optional[RMB])->CardRechargeOrder
- log = {
- 'devNo': device.devNo,
- 'logicalCode': device.logicalCode,
- 'sendMoney': sendMoney.mongo_amount,
- 'preBalance': preBalance.mongo_amount,
- 'code': device.get('devType', {}).get('code', ''),
- 'result': result,
- 'description': description,
- 'time': datetime.datetime.now()
- }
- if syncBalance:
- log['syncBalance'] = syncBalance.mongo_amount
- self.operationLog.append(log)
- if result == ErrorCode.SUCCESS:
- self.status = 'finished'
- return self.save()
- def update_after_recharge_id_card(self, device, balance, preBalance):
- # type: (Optional[DeviceDict], RMB, RMB)->CardRechargeOrder
- if device:
- log = {
- 'devNo': device.devNo,
- 'logicalCode': device.logicalCode,
- 'balance': balance.mongo_amount,
- 'preBalance': preBalance.mongo_amount,
- 'code': device.get('devType', {}).get('code', ''),
- 'time': datetime.datetime.now()
- }
- else:
- log = {
- 'balance': balance.mongo_amount,
- 'preBalance': preBalance.mongo_amount,
- 'time': datetime.datetime.now()
- }
- self.operationLog.append(log)
- self.status = 'finished'
- return self.save()
- @staticmethod
- def new_one(openId, cardId, cardNo, money, coins, group, rechargeId, rechargeType = 'netpay'):
- # type:(str, str, str, RMB, RMB, GroupDict, ObjectId, str)->CardRechargeOrder
- newRcd = CardRechargeOrder(
- orderNo = OrderNoMaker.make_order_no_32(identifier = cardNo,
- main_type = OrderMainType.PAY,
- sub_type = UserPaySubType.CARD_RECHARGE_RECORD),
- cardId = str(cardId),
- cardNo = cardNo,
- openId = openId,
- money = money,
- coins = RMB(coins.amount),
- status = 'finishedPay',
- rechargeType = rechargeType,
- dealerId = group.ownerId,
- groupId = group.groupId,
- address = group.address,
- groupName = group.groupName,
- rechargeNo = rechargeId
- )
- logger.info('make card recharge order = %s' % repr(newRcd))
- try:
- return newRcd.save()
- except Exception as e:
- logger.exception('save cardId = %s; cardNo = %s; recharge error = %s' % (cardId, cardNo, e))
- return None
- def to_detail(self):
- # type: ()->dict
- """
- :return:
- """
- return {
- 'orderNo': self.orderNo,
- 'cardNo': self.cardNo,
- 'openId': self.openId,
- 'groupId': self.groupId,
- 'amount': self.money,
- 'devNo': '',
- 'logicalCode': '',
- 'devType': '',
- 'groupNumber': '',
- 'groupName': self.groupName,
- 'address': self.address
- }
- @classmethod
- def get_by_rechargeRecord(cls, record):
- # type:(RechargeRecord)->CardRechargeOrder
- return cls.objects(rechargeNo = record.id).first()
- class CardRechargeRecord(Searchable):
- orderNo = StringField(verbose_name = "关联订单号", default = "") # 关联订单处理卡退费
- cardId = StringField(verbose_name = "实体卡ID", default = "")
- cardNo = StringField(verbose_name = "实体卡号", default = "")
- openId = StringField(verbose_name = "openId", default = "")
- ownerId = ObjectIdField(verbose_name = "dealerId")
- money = MonetaryField(verbose_name = "付款金额", default = RMB('0.00'))
- coins = MonetaryField(verbose_name = "充值金额", default = VirtualCoin('0.00'))
- chargeBalance = MonetaryField(verbose_name="充值余额", default=RMB(0))
- bestowBalance = MonetaryField(verbose_name="赠送余额", default=RMB(0))
- preChargeBalance = MonetaryField(verbose_name="之前充值余额", default=RMB(0))
- preBestowBalance = MonetaryField(verbose_name="之前赠送余额", default=RMB(0))
- devNo = StringField(verbose_name = "设备ID", default = "")
- devTypeCode = StringField(verbose_name = "设备类型编码", default = "")
- logicalCode = StringField(verbose_name = "设备逻辑编码", default = "")
- groupId = StringField(verbose_name = "设备地址编号", default="")
- address = StringField(verbose_name = "设备地址", default = "")
- groupNumber = StringField(verbose_name = "设备", default = "")
- groupName = StringField(verbose_name = "交易场地", default = "")
- #: 在部分场合,设备会失败启动,但这时应该添加该字段为False方便追溯
- remarks = StringField(verbose_name="备注", default="")
- dateTimeAdded = DateTimeField(default=datetime.datetime.now, verbose_name='生成时间')
- sequanceNo = StringField(verbose_name="交易流水号,有些刷卡的会上报", default="")
- status = StringField(verbose_name="状态", default="")
- rechargeType = StringField(verbose_name="充值类型,一般分网络充值、线下设备充值", default="")
- meta = {
- "collection": "cardRechargeRecord",
- "db_alias": "default",
- # "shard_key":('cardId',),
- }
- @property
- def amount(self):
- return self.money
- @property
- def balance(self):
- return self.chargeBalance + self.bestowBalance
- @property
- def preBalance(self):
- return self.preChargeBalance + self.preBestowBalance
- @property
- def dealer(self):
- return Dealer.objects(id=self.ownerId).get()
- @staticmethod
- def make_no():
- timestamp = generate_timestamp_ex()
- random_int = random.randint(1, 1000)
- return "%d%03d" % (timestamp, random_int)
- @classmethod
- def add_self_card_record(cls, cardId, group, money, device):
- newRcd = CardRechargeRecord(
- cardId = cardId,
- ownerId = ObjectId(group.ownerId),
- money = money,
- coins = money,
- groupId = group.groupId,
- address = group.address,
- groupName = group.groupName,
- status = 'success',
- rechargeType = 'dealer_oper',
- devNo = device.devNo,
- devTypeCode = device.get('devType', {}).get('code', ''),
- logicalCode = device.logicalCode,
- groupNumber = device.groupNumber
- )
- try:
- return newRcd.save()
- except Exception as e:
- logger.exception('save card consume rcd error=%s' % e)
- @staticmethod
- def add_record(card, group, order, device=None):
- # type:(Card, GroupDict, CardRechargeOrder, Optional[DeviceDict])->CardRechargeRecord
- newRcd = CardRechargeRecord(
- cardId=str(card.id),
- cardNo=card.cardNo,
- openId=card.openId,
- ownerId=ObjectId(group.ownerId),
- money=order.money,
- coins=order.coins,
- chargeBalance=order.chargeAmount + card.chargeBalance,
- bestowBalance=order.bestowAmount + card.bestowBalance,
- preChargeBalance=card.chargeBalance,
- preBestowBalance=card.bestowBalance,
- groupId=group.groupId,
- address=group.address,
- groupName=group.groupName,
- status='success',
- rechargeType=order.rechargeType
- )
- if device:
- newRcd.devNo = device.devNo
- newRcd.logicalCode = device.logicalCode
- newRcd.devTypeCode = device.devTypeCode
- newRcd.groupNumber = device.groupNumber
- try:
- return newRcd.save()
- except Exception as e:
- logger.exception('save card consume rcd error=%s' % e)
- class CardConsumeRecord(Searchable):
- orderNo = StringField(verbose_name = "订单号", default = "")
- openId = StringField(verbose_name = "微信ID", default = "")
- cardId = StringField(verbose_name = "实体卡ID", default = "")
- money = MonetaryField(verbose_name = "消耗的钱(硬币)", default = RMB('0.00'))
- balance = MonetaryField(verbose_name = "当前余额(硬币)", default = RMB('0.00'))
- devNo = StringField(verbose_name = "设备ID", default = "")
- devType = StringField(verbose_name = "设备类型", default = "")
- logicalCode = StringField(verbose_name = "设备逻辑编码", default = "")
- groupId = StringField(verbose_name = "设备地址编号", default = "")
- address = StringField(verbose_name = "设备地址", default = "")
- groupNumber = StringField(verbose_name = "设备", default = "")
- groupName = StringField(verbose_name = "交易场地", default = "")
- result = StringField(verbose_name = "消费结果", default = "")
- remarks = StringField(verbose_name = "备注", default = "")
- desc = StringField(verbose_name = "描述", default = "")
- dateTimeAdded = DateTimeField(verbose_name = '生成时间', default = datetime.datetime.now)
- finishedTime = DateTimeField(verbose_name = '结束时间', default = datetime.datetime.now)
- servicedInfo = DictField(verbose_name = '服务内容', default = {})
- sid = StringField(verbose_name = "从设备上获取的流水号", default = "")
- linkedConsumeRcdOrderNo = StringField(verbose_name = u'关联的消费订单号.发布后去掉, 两者一致', default = '')
- meta = {
- "collection": "CardConsumeRecord",
- "db_alias": "default",
- # "shard_key":('orderNo',),
- }
- @staticmethod
- def make_no():
- timestamp = generate_timestamp_ex()
- random_int = random.randint(1, 1000)
- return "%d%03d" % (timestamp, random_int)
- class ServiceProgress(Searchable):
- open_id = StringField(verbose_name = 'open id', default = '')
- device_imei = StringField(verbose_name = 'device imei', default = '')
- port = IntField(verbose_name = 'port', default = 0)
- isFinished = BooleanField(verbose_name = 'isFinished', default = False)
- devTypeCode = StringField(verbose_name = 'devTypeCode', default = '')
- cardId = StringField(verbose_name = 'cardNo', default = '')
- attachParas = DictField(verbose_name = 'attachParas', default = {})
- consumeOrder = DictField(verbose_name = 'consumeOrder', default = {})
- finished_time = IntField(verbose_name = 'finished time', default = 0)
- start_time = IntField(verbose_name = 'start_time', default = 0)
- consumes = ListField(verbose_name = "对应的所有消费记录单号", default = [])
- # 应该用一个状态表示服务的执行情况,包括等待服务、服务正在进行、服务结束。只有我们才需要此字段
- # 但是只有我们自己的主板支持每个订单的执行情况反馈以及查询,所以没有办法,以前都是裹在一起,现在分开逻辑更清晰。
- status = StringField(verbose_name = u'执行状态', default = 'running') # waiting/working/finished/failed
- weifuleOrderNo = StringField(verbose_name = u'微付乐订单编号,设备也会产生订单', default = '')
- runningTime = DateTimeField(verbose_name = u'开始运行的时间')
- datetimeAdded = DateTimeField(verbose_name = "时间", default = datetime.datetime.now)
- expireAt = DateTimeField(verbose_name = u"TTL到期时间", default = None)
- meta = {
- 'collection': 'service_progress',
- 'db_alias': 'logdata'
- }
- @property
- def used_port(self):
- """
- 兼容性属性. 以前是整形, 后面全部为字符串型
- :return:
- """
- return str(self.port)
- @property
- def order_no(self):
- return self.attachParas.get('orderNo', None)
- # 更新progress,以及关联的消费日志记录。这个函数一般是服务结束的时候调用,以刷新progress,有时候,结束时
- # 才扣费的场景,扣费的时候会直接把使用后的消耗,记录到消费日志中,就不需要刷新消费日志
- @staticmethod
- def update_progress_and_consume_rcd(ownerId, queryDict, consumeDict, updateConsume = True, progressDict = None):
- progressDict = {
- 'isFinished': True, 'finished_time': int(time.time())
- } if progressDict is None else progressDict
- if 'isFinished' in progressDict and progressDict['isFinished']:
- progressDict.update({'expireAt': datetime.datetime.now()})
- rcds = ServiceProgress.get_collection().find(queryDict).sort('start_time')
- if rcds.count() == 0:
- logger.error('can not find the progress info')
- return
- orderIdList, orderNoList, cardOrderNoList = [], [], []
- for rcd in rcds: # type: Dict
- consumeOrder = rcd.get('consumeOrder', {})
- if 'consumeRecordId' in consumeOrder and consumeOrder['consumeRecordId']:
- orderIdList.append(consumeOrder['consumeRecordId'])
- elif 'orderNo' in consumeOrder and consumeOrder['orderNo']:
- orderNoList.append(consumeOrder['orderNo'])
- if 'cardOrderNo' in consumeOrder:
- cardOrderNoList.append(consumeOrder['cardOrderNo'])
- # 更新进程的数据
- try:
- ServiceProgress.get_collection().update(queryDict, {'$set': progressDict}, multi = True)
- except Exception, e:
- logger.exception('update service progress e=%s' % e)
- if not updateConsume: # 不需要更新就直接返回
- return
- # 更新订单中的时间以及服务信息。如果是卡的信息,因为找不到order,会直接pass
- # 如果是多条消费单,但是最终只有一条结束事件的场景,统计以及消费信息只记录到第一单上
- isFinished = progressDict and progressDict.get('isFinished', False)
- consume_orders = []
- for orderNo in orderNoList:
- rcd = ConsumeRecord.objects(ownerId = ownerId, orderNo = orderNo).first() # type: ConsumeRecord
- consume_orders.append(rcd)
- for orderId in orderIdList:
- rcd = ConsumeRecord.objects(ownerId = ownerId, id = orderId).first() # type: ConsumeRecord
- consume_orders.append(rcd)
- count = 0
- uart_data = consumeDict.pop('uartData', None)
- for rcd in consume_orders: # type: ConsumeRecord
- try:
- count += 1
- pre_aggInfo = rcd.aggInfo
- # 如果是结束更新,相应的信息只需要更新到第一条中。非结束的,全部都刷新。
- if not isFinished:
- updated = rcd.update(
- finishedTime = datetime.datetime.now(),
- servicedInfo = consumeDict,
- status = ConsumeRecord.Status.FINISHED)
- elif count == 1:
- if uart_data:
- updated = rcd.update(
- finishedTime = datetime.datetime.now(),
- servicedInfo = consumeDict,
- status = ConsumeRecord.Status.FINISHED,
- attachParas__uartData = uart_data)
- else:
- updated = rcd.update(
- finishedTime = datetime.datetime.now(),
- servicedInfo = consumeDict,
- status = ConsumeRecord.Status.FINISHED)
- else:
- continue
- if not updated:
- logger.error('%r updated failed' % (rcd,))
- if isFinished and count == 1: # 只需要汇总一次使用的数据即可
- try:
- valueDict = {}
- for kind, value in consumeDict.items():
- if kind in DEALER_CONSUMPTION_AGG_KIND.choices():
- if kind in pre_aggInfo:
- #: 会在发起service阶段更新一次报表
- #: 此处为结束时更新, 应该更新新数据 - 初始更新的数据
- valueDict[kind] = float(str(value)) - float(str(pre_aggInfo[kind]))
- else:
- valueDict[kind] = value
- status = rcd.update_agg_info(valueDict)
- if status:
- record_consumption_stats(rcd)
- else:
- logger.error(
- '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (rcd,))
- except Exception, e:
- logger.exception('update agg info error=%s' % e)
- except Exception as e:
- logger.exception('update consume rcd e=%s' % e)
- continue
- for orderNo in cardOrderNoList:
- if orderNo == '':
- continue
- try:
- CardConsumeRecord.get_collection().update_one({'orderNo': orderNo}, {
- '$set': {'finished_time': int(time.time()), 'servicedInfo': consumeDict}})
- except Exception, e:
- logger.exception('update consume rcd e=%s' % e)
- continue
- @staticmethod
- def register_card_service(dev, port, card, consumeOrder = None, finishedTime = None):
- # type:(DeviceDict, int, Card, dict, int)->None
- consumeOrder = {} if consumeOrder is None else consumeOrder
- finishedTime = int(time.time()) + 24 * 60 * 60 if finishedTime is None else finishedTime
- ServiceProgress.get_collection().insert({
- 'device_imei': dev.devNo,
- 'devTypeCode': dev.devTypeCode,
- 'cardId': str(card.id),
- 'port': port,
- 'finished_time': finishedTime,
- 'start_time': int(time.time()),
- 'open_id': card.openId,
- 'consumeOrder': consumeOrder,
- 'isFinished': False,
- 'attachParas': {},
- 'expireAt': datetime.datetime.now() + datetime.timedelta(days = 91)
- })
- @staticmethod
- def register_card_service_for_weifule(dev, port, card, consumeOrder):
- ServiceProgress.get_collection().insert({
- 'device_imei': dev['devNo'],
- 'devTypeCode': dev['devType']['code'],
- 'cardId': str(card.id),
- 'port': port,
- 'finished_time': int(time.time()) + 24 * 60 * 60,
- 'start_time': int(time.time()),
- 'open_id': card.openId,
- 'consumeOrder': consumeOrder,
- 'isFinished': False,
- 'attachParas': {},
- 'status': 'waiting',
- 'weifuleOrderNo': consumeOrder['orderNo'],
- 'expireAt': datetime.datetime.now() + datetime.timedelta(days = 91)
- })
- @classmethod
- def new_progress_for_order(cls, order, device, cache_info):
- # type:(ConsumeRecord, DeviceDict, dict)->None
- try:
- consumeOrder = {
- 'consumeRecordId': str(order.id),
- 'orderNo': order.orderNo,
- 'coin': VirtualCoin(cache_info['coins']).mongo_amount,
- 'consumeType': cache_info.get('consumeType') or (
- 'mobile_vcard' if u'虚拟卡' in order.remarks else 'mobile')
- }
- if 'money' in cache_info:
- consumeOrder.update({'money': RMB(cache_info['money']).mongo_amount})
- if 'unit' in cache_info:
- consumeOrder.update({'unit': cache_info['unit']})
- if 'subOrderNos' in cache_info:
- consumeOrder.update({'subOrderNos': cache_info['subOrderNos']})
- if 'needKind' in cache_info and 'needValue' in cache_info:
- consumeOrder.update({
- cache_info['needKind']: cache_info['needValue']
- })
- if 'cardId' in cache_info:
- consumeOrder.update({'cardId': cache_info['cardId']})
- update_dict = {
- 'open_id': cache_info['openId'],
- 'device_imei': device.devNo,
- 'devTypeCode': device['devType']['code'],
- 'port': order.used_port,
- 'attachParas': order.attachParas,
- 'start_time': int(
- Arrow.fromdatetime(order.startTime, tzinfo = settings.TIME_ZONE).timestamp),
- 'finished_time': int(cache_info['estimatedTs']),
- 'consumeOrder': consumeOrder,
- 'isFinished': False,
- 'expireAt': datetime.datetime.now() + datetime.timedelta(days = 91)
- }
- cls.objects(open_id = order.openId,
- device_imei = device.devNo,
- port = order.used_port).update_one(upsert = True, **update_dict)
- except Exception as e:
- logger.exception(e)
- @classmethod
- def finish_progress_for_order(cls, order, device):
- # type:(ConsumeRecord, DeviceDict)->None
- cls.get_collection().update_one(filter = {
- 'open_id': order.openId,
- 'device_imei': device.devNo,
- 'port': int(order.used_port),
- 'consumeOrder.consumeRecordId': str(order.id)
- }, update = {
- '$set': {
- 'isFinished': True,
- 'finished_time': Arrow.fromdatetime(order.finishedTime,
- tzinfo = settings.TIME_ZONE).timestamp,
- 'expireAt': datetime.datetime.now()
- }
- }, upsert = False)
- class Card(Searchable):
- cardNo = StringField(verbose_name="卡号", default="")
- cardType = StringField(verbose_name="卡类型", default="")
- openId = StringField(verbose_name="绑定的微信", default="")
- nickName = StringField(verbose_name="用户昵称", default="")
- productAgentId = StringField(verbose_name="当前用户绑定的平台代理商", default = "")
- cardName = StringField(verbose_name="持卡人姓名", default="")
- phone = StringField(verbose_name="手机号码", default="")
- # 绑定的管理APP信息
- managerialAppId = StringField(verbose_name="管理公众号AppId", default="")
- managerialOpenId = StringField(verbose_name="管理openId", default="")
- dealerId = StringField(verbose_name="经销商ID", default="")
- groupId = StringField(verbose_name="设备组ID", default="")
- agentId = StringField(verbose_name="代理商ID", default="")
- # 以下信息为卡固有信息
- remarks = StringField(verbose_name="备注", default="")
- chargeBalance = MonetaryField(verbose_name="充值余额", default=RMB(0))
- bestowBalance = MonetaryField(verbose_name=u"赠送余额", default=RMB(0))
- isHaveBalance = BooleanField(verbose_name="是否能够获取到卡的余额数据", default=True)
- lastMaxBalance = MonetaryField(verbose_name="最后一次充卡金额", default=RMB('0.00'))
- boundVirtualCardId = ObjectIdField(verbose_name=u"绑定的虚拟卡券ID")
- showBalance = MonetaryField(verbose_name=u"显示到设备上的卡余额,只有微付乐才会用此字段", default=RMB('0.00'))
- # 最近一次刷卡的设备
- devNo = StringField(verbose_name="设备IMEI", default="")
- devTypeCode = StringField(verbose_name="设备类型代码", default="")
- frozen = BooleanField(verbose_name="卡挂失状态", default=False)
- status = StringField(verbose_name="卡状态", default='active')
- dateTimeAdded = DateTimeField(verbose_name=u"添加卡的时间", default=datetime.datetime.now)
- attachParas = DictField(verbose_name='附加参数', default={})
- ongoingList = ListField(field=DictField(), verbose=u'冻结的金额')
- search_fields = ('cardNo', 'cardName', 'phone')
- meta = {
- "collection": "recharge_card",
- "db_alias": "default",
- }
- def __repr__(self):
- return '<Card(id={}, cardNo={})>'.format(str(self.id), self.cardNo)
- @cached_property
- def group(self):
- if not self.groupId:
- return None
- return Group.get_group(self.groupId)
- @cached_property
- def user(self):
- if not self.isBinded:
- return None
- return MyUser.objects.filter(openId=self.openId, groupId=self.groupId).first()
- @property
- def balance(self):
- return self.chargeBalance + self.bestowBalance
- @property
- def nickname(self):
- return self.nickName
- @property
- def is_id_card(self):
- # type: ()->bool
- return self.cardType == RECHARGE_CARD_TYPE.ID
- @property
- def is_ic_card(self):
- # type: ()->bool
- return self.cardType == RECHARGE_CARD_TYPE.IC
- @property
- def isBinded(self):
- """
- 用户的绑定状态
- """
- return True if self.openId and self.openId != Const.DEFAULT_CARD_OPENID else False
- def recharge(self, money, bestowMoney):
- assert isinstance(money, RMB), 'coins had to be VirtualCoin'
- assert isinstance(bestowMoney, RMB), 'money has to be RMB'
- updated = self.update(
- inc__chargeBalance=money,
- inc__bestowBalance=bestowMoney,
- )
- if not updated:
- raise PostPayOrderError(u'余额和累计充值更新失败')
- return self.reload()
- def account_consume(self, order): # type:(ConsumeRecord) -> None
- self.reload()
- payment = order.payment
- # 获取支付的钱
- consumeAmount = payment.actualAmount
- consumeBestowAmount = payment.totalAmount - consumeAmount
- # 获取当前的余额
- CardBalanceLog.consume(
- self,
- afterAmount=self.chargeBalance,
- afterBestowAmount=self.bestowBalance,
- consumeAmount=consumeAmount,
- consumeBestowAmount=consumeBestowAmount,
- order=order
- )
- def account_refund(self, order): # type:(ConsumeRecord) -> None
- self.reload()
- refund = order.refund
- # 获取支付的钱
- refundAmount = refund.actualAmount
- refundBestowAmount = refund.totalAmount - refundAmount
- CardBalanceLog.refund(
- self,
- afterAmount=self.chargeBalance,
- afterBestowAmount=self.bestowBalance,
- refundAmount=refundAmount,
- refundBestowAmount=refundBestowAmount,
- order=order
- )
- def account_recharge(self, order): # type:(RechargeRecord) -> None
- # 获取支付的钱
- chargeAmount = order.chargeAmount
- bestowAmount = order.bestowAmount
- CardBalanceLog.recharge(
- self,
- afterAmount=self.chargeBalance,
- afterBestowAmount=self.bestowBalance,
- chargeAmount=chargeAmount,
- chargeBestowAmount=bestowAmount,
- order=order
- )
- @classmethod
- def fake_one(cls, groupId, cardId='fake_id', cardNo='fake'):
- return cls(id=cardId, cardNo=cardNo, openId='', productAgentId='', groupId=groupId)
- @staticmethod
- def get_card_status(cardId):
- status = serviceCache.get(cardId, 'idle')
- return status
- @staticmethod
- def set_card_status(cardId, status):
- serviceCache.set(cardId, status, 10)
- @staticmethod
- def get_dev_cur_card(devNo):
- return serviceCache.get('%s_cardId' % devNo, None)
- @staticmethod
- def set_dev_cur_card(devNo, cardInfo):
- serviceCache.set('%s_cardId' % devNo, cardInfo, 90)
- @property
- def bound_virtual_card(self):
- # type: ()->Optional[UserVirtualCard]
- if self.boundVirtualCardId:
- now = datetime.datetime.now()
- virtual_card = UserVirtualCard.objects(id = self.boundVirtualCardId,
- expiredTime__gte = now,
- startTime__lte = now).first()
- return virtual_card
- else:
- return None
- def bind_virtual_card(self, card):
- # type:(UserVirtualCard)->None
- return self.update(boundVirtualCardId = card.id)
- def unbind_virtual_card(self, card):
- # type:(UserVirtualCard)->None
- """
- 以后也许会支持多张卡
- :param card:
- :return:
- """
- return self.update(boundVirtualCardId = None)
- # 这个那种有余额的卡,才会调用
- @staticmethod
- def update_balance(cardId, balance):
- card = Card.objects(id=cardId).get()
- result = card.update(balance = balance)
- if not result:
- logger.error('update error, cardId=%s, balance=%s' % (cardId, balance))
- return result
- @staticmethod
- def check_card_no(cardNo):
- """
- 检查实体卡卡号的合法性
- :param cardNo:
- :return:
- """
- if CARD_NO_RE.match(cardNo):
- return True
- return False
- @classmethod
- def check_swap_card_no(cls, cardNo, dealerId, agentId):
- try:
- card = cls.objects.get(cardNo=cardNo, agentId=agentId)
- except DoesNotExist:
- return True, ""
- if card.dealerId and card.dealerId != dealerId:
- return False, u"该卡已经被其他经销商绑定,请确认卡号无误"
- if card.openId:
- return False, u"该卡号已经被其他用户绑定,请确认卡号无误"
- if CardConsumeRecord.objects.filter(cardId=str(card.id)):
- return False, u"该卡号存在用户使用记录, 请确认卡号无误"
- card.delete()
- return True, ""
- @staticmethod
- def record_dev_card_no(devNo, cardNo):
- serviceCache.set('%s-cardno' % devNo, cardNo, 2 * 60)
- @staticmethod
- def get_dev_card_no(devNo):
- return serviceCache.get('%s-cardno' % devNo, None)
- @staticmethod
- def clear_dev_card_no(devNo):
- serviceCache.delete('%s-cardno' % devNo)
- def to_dict(self):
- data = {
- "cardId": str(self.id),
- "cardNo": self.cardNo,
- "cardName": self.cardName,
- "phone": self.phone,
- "cardType": self.cardType,
- "remarks": self.remarks,
- "frozen": self.frozen,
- "balance": self.balance,
- "lastMaxBalance": self.lastMaxBalance,
- "bindStatus": self.isBinded,
- }
- return data
- def freeze_transaction_id(self, freezeType):
- return '{}_{}'.format(freezeType, str(self.id))
- @classmethod
- def freeze_balance(cls, transaction_id, payment):
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- chargeBalanceField = cls.chargeBalance.name
- bestowBalanceField = cls.bestowBalance.name
- for deduct in payment.deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- 'ongoingList.transaction_id': {
- '$ne': transaction_id
- }
- }
- update = {
- '$inc': {
- chargeBalanceField: (-RMB(deduct[chargeBalanceField])).mongo_amount,
- bestowBalanceField: (-RMB(deduct[bestowBalanceField])).mongo_amount
- },
- '$addToSet': {
- 'ongoingList': {
- 'transaction_id': transaction_id,
- chargeBalanceField: deduct[chargeBalanceField],
- bestowBalanceField: deduct[bestowBalanceField]
- }
- }
- }
- bulker.update(query, update)
- result = bulker.execute()
- logger.debug(result['info'])
- if result['success'] == 0:
- raise ServiceException({'result': 0, 'description': u"扣款失败(1001)"})
- else:
- if len(result['info']['writeErrors']) != 0:
- raise ServiceException({'result': 0, 'description': u"扣款失败(1002)"})
- else:
- return True
- @classmethod
- def clear_frozen_balance(cls, transaction_id, refund):
- try:
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- chargeBalanceField = cls.chargeBalance.name
- bestowBalanceField = cls.bestowBalance.name
- for deduct in refund.deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- 'ongoingList': {
- '$elemMatch': {
- 'transaction_id': transaction_id
- }
- }
- }
- update = {
- '$inc': {
- chargeBalanceField: deduct[chargeBalanceField],
- bestowBalanceField: deduct[bestowBalanceField],
- },
- '$pull': {
- 'ongoingList': {
- 'transaction_id': transaction_id
- }
- }
- }
- bulker.update(query, update)
- result = bulker.execute()
- logger.debug(result['info'])
- if result['success'] == 0:
- return False
- else:
- if len(result['info']['writeErrors']) != 0:
- return False
- else:
- return True
- except Exception as e:
- logger.exception(e)
- return False
- @classmethod
- def recover_frozen_balance(cls, transaction_id, deduct_list):
- bulker = BulkHandlerEx(cls.get_collection()) # type: BulkHandlerEx
- for deduct in deduct_list:
- query = {
- '_id': ObjectId(deduct['id']),
- 'ongoingList': {
- '$elemMatch': {'transaction_id': transaction_id}}}
- update = {
- '$inc': {
- 'balance': deduct['coins']
- },
- '$pull': {
- 'ongoingList': {
- 'transaction_id': transaction_id, 'frozenCoins': deduct['coins']
- }}
- }
- bulker.update(query, update)
- result = bulker.execute()
- if result['success'] == 0:
- logger.error(result['info'])
- return False
- else:
- if len(result['info']['writeErrors']) != 0:
- logger.error(result['info'])
- return False
- else:
- return True
- def clear_card(self):
- return self.update(
- cardType = '',
- openId = '',
- nickName = '',
- productAgentId = '',
- cardName = '',
- phone = '',
- managerialAppId = '',
- managerialOpenId = '',
- dealerId = '',
- groupId = '',
- remarks = '',
- isHaveBalance = True,
- lastMaxBalance = RMB('0.0'),
- devNo = '',
- devTypeCode = '',
- frozen = False,
- status = 'active',
- boundVirtualCardId = None,
- attachParas = {}
- )
- class RefundMoneyRecord(RefundOrderBase):
- """
- 用户退款记录
- """
- refIncomeOrder = ObjectIdField(verbose_name = u'对应收益单', default = None)
- coins = MonetaryField(verbose_name = u"清理用户金币数", default = None)
- meta = {
- "collection": "RefundMoneyRecord",
- "db_alias": "default",
- }
- @classmethod
- def issue(cls, order, refundCash, **extraInfo):
- # type:(RechargeRecord, RMB, dict)->RefundMoneyRecord
- extraInfo.update({'v': 2})
- if order.via in [USER_RECHARGE_TYPE.RECHARGE_CARD, USER_RECHARGE_TYPE.RECHARGE_VIRTUAL_CARD]:
- identifier = order.attachParas['cardNo']
- elif order.via in [USER_RECHARGE_TYPE.RECHARGE_MONTHLY_PACKAGE]:
- identifier = order.attachParas.get('cardId')
- else:
- identifier = order.logicalCode
- refund_order_no = OrderNoMaker.make_order_no_32(
- identifier = identifier,
- main_type = OrderMainType.REFUND,
- sub_type = RefundSubType.REFUND)
- return cls(
- rechargeObjId = order.id,
- # refundSeq=next_seq,
- orderNo = refund_order_no,
- money = refundCash,
- status = cls.Status.PROCESSING,
- datetimeAdded = datetime.datetime.now(),
- extraInfo = dict_field_with_money(extraInfo),
- payAppType = order.pay_app_type
- ).save()
- @property
- def pay_sub_order(self):
- # type: ()->RechargeRecord
- if not hasattr(self, '__pay_sub_order__'):
- from apps.web.common.proxy import ClientRechargeModelProxy
- pay_order = ClientRechargeModelProxy.get_one(id = str(self.rechargeObjId)) # type: RechargeRecord
- setattr(self, '__pay_sub_order__', pay_order)
- return getattr(self, '__pay_sub_order__')
- @pay_sub_order.setter
- def pay_sub_order(self, order):
- setattr(self, '__pay_sub_order__', order)
- @property
- def pay_app_type(self):
- if self.payAppType:
- return self.payAppType
- else:
- return self.pay_sub_order.pay_app_type
- @property
- def refund_income_order(self):
- if not hasattr(self, '__refund_income_order__'):
- if self.refIncomeOrder:
- order = RechargeRecord.objects(
- id = self.refIncomeOrder).first()
- else:
- # 对老的方式的兼容
- order = RechargeRecord.objects(
- id = self.pay_sub_order.extraInfo['refRefund'][0]['objId']).first()
- setattr(self, '__refund_income_order__', order)
- return getattr(self, '__refund_income_order__')
- @refund_income_order.setter
- def refund_income_order(self, obj):
- setattr(self, '__refund_income_order__', obj)
- @property
- def user(self):
- # type: ()->MyUser
- return self.pay_sub_order.user
- @property
- def notify_url(self):
- if self.pay_app_type in [PayAppType.WECHAT]:
- return REFUND_NOTIFY_URL.WECHAT_REFUND_BACK
- else:
- return None
- @property
- def checkWallet(self):
- return self.extraInfo.get('checkWallet', False)
- @property
- def operatorId(self):
- return self.extraInfo.get('operatorId', None)
- @property
- def is_new_version(self):
- return self.extraInfo.get('v', 1) > 1
- @property
- def deductCoins(self):
- """
- 兼容以前数据.退现金同时被扣费的钱包余额。后续需要建模为被扣的描述(钱包就是被扣金额, 月卡等有自己的描述)
- :return:
- """
- if self.is_new_version:
- return VirtualCoin(self.extraInfo.get('deductCoins', 0))
- else:
- return self.coins
- @property
- def frozenCoins(self):
- """
- 不退现金情况下应该退的钱包余额
- :return:
- """
- if self.is_new_version:
- return VirtualCoin(self.extraInfo.get('frozenCoins', 0))
- else:
- return self.coins
- class VCardConsumeRecord(OrderRecordBase):
- orderNo = StringField(verbose_name = "订单号", default = "")
- cardId = StringField(verbose_name = "实体卡ID", default = "")
- dealerId = StringField(verbose_name = "经销商ID", default = "")
- # : 在部分场合,设备会失败启动,但这时应该添加该字段为False方便追溯
- remarks = StringField(verbose_name = "备注", default = "")
- desc = StringField(verbose_name = "描述", default = "")
- finishedTime = DateTimeField(verbose_name = '结束时间', default = datetime.datetime.now)
- servicedInfo = DictField(verbose_name = '服务内容', default = {})
- attachParas = DictField(verbose_name = '服务内容', default = {})
- # 本次消费消耗的额度
- consumeData = DictField(verbose_name = '消耗的额度', default = {})
- # 日限额消耗的额度
- consumeDayData = DictField(verbose_name = '消耗的额度', default = {})
- meta = {
- "collection": "VCardConsumeRecord",
- "db_alias": "default",
- }
- @staticmethod
- def make_no(identifier):
- # time:14;main_type:1;sub_type:1;identifier:15;extra:5
- return '{time}{main_type}{sub_type}{identifier}{reserved}'.format(
- time = datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
- main_type = OrderMainType.CONSUME,
- sub_type = UserConsumeSubType.VIRTUAL_CARD,
- identifier = '{:0>15}'.format(identifier),
- reserved = get_random_str(5, string.digits + string.uppercase))
- @staticmethod
- def paginate(cardId, pageIndex, pageSize):
- records = VCardConsumeRecord.objects.filter(cardId = cardId) \
- .order_by('-dateTimeAdded').skip((pageIndex - 1) * pageSize).limit(pageSize)
- if not records:
- return 0, []
- dataList = []
- for record in records:
- newData = {
- 'id': str(record.id),
- 'createdTime': record.dateTimeAdded.strftime(Const.DATETIME_FMT),
- 'address': record.address,
- 'devNo': record.devNo,
- 'openId': record.openId,
- 'nickname': record.nickname,
- 'logicalCode': record.logicalCode,
- 'groupNumber': record.groupNumber,
- 'groupName': record.groupName,
- 'unit': record.consumeData.get("unit", ""),
- 'devTypeCode': record.devTypeCode,
- 'devTypeName': record.dev_type_name,
- 'isNormal': True,
- 'ownerId': record.dealerId
- }
- amount = record.consumeData.get("count", "")
- if amount:
- amount = Quantity(amount)
- newData.update({'amount': amount})
- if record.servicedInfo:
- newData.update(record.servicedInfo)
- dataList.append(newData)
- return len(dataList), dataList
- @property
- def amount(self):
- _amount = self.consumeData.get("count", None)
- if _amount is not None:
- return str(Quantity(_amount))
- else:
- return ""
- @property
- def unit(self):
- return self.consumeData.get("unit", "")
- @property
- def created_date(self):
- return self.dateTimeAdded
- @property
- def completion_date(self):
- return self.finishedTime
- @property
- def device_start_time(self):
- return self.dateTimeAdded
- @property
- def device_finished_time(self):
- return self.finishedTime
- def to_user_detail(self):
- """
- 消费记录对于用户端显示的详细信息
- :return:
- """
- data = {
- 'id': str(self.id),
- 'orderNo': self.orderNo,
- 'createdTime': self.dateTimeAdded.strftime(Const.DATETIME_FMT),
- 'deviceStatTime': self.device_start_time,
- 'address': self.address,
- 'devNo': self.devNo,
- 'logicalCode': self.logicalCode,
- 'groupNumber': self.groupNumber,
- 'groupName': self.groupName,
- 'devTypeCode': self.devTypeCode,
- 'devTypeName': self.dev_type_name,
- 'isNormal': True,
- 'amount': self.amount,
- 'unit': self.unit,
- 'ownerId': self.dealerId,
- 'consumeType': 'mobile_vcard',
- 'openId': self.openId,
- 'userNickname': self.nickname
- }
- # 兼容之前的订单 凡是没有订单状态机的订单,一律视为 之前的订单
- if hasattr(self, "state") and self.state:
- data.update({"orderStatus": self.state})
- if self.servicedInfo:
- data.update(self.servicedInfo)
- return data
- @property
- def ownerId(self):
- return self.dealerId
- @property
- def owner(self):
- from apps.web.dealer.models import Dealer
- _attr_name = '__my_owner__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- if not self.dealerId:
- return None
- else:
- dealer = Dealer.objects(id = self.dealerId).first()
- setattr(self, _attr_name, dealer)
- return dealer
- @property
- def summary(self):
- return {
- 'id': str(self.id),
- 'consumeType': 'mobile_vcard',
- 'ownerId': self.ownerId,
- 'logicalCode': self.logicalCode,
- 'groupName': self.groupName,
- 'devTypeName': self.dev_type_name,
- 'amount': self.amount,
- 'unit': self.unit,
- 'createdTime': self.dateTimeAdded.strftime(Const.DATETIME_FMT),
- }
- VirtualCardQuotaList = List[Dict[str, Union[float, unicode]]]
- class UserVirtualCard(Searchable):
- cardNo = StringField(verbose_name = "卡编号", default = "")
- cardTypeId = StringField(verbose_name = u'卡的类型ID', default = '') # 如果卡停止销售了,这里就可以不允许续充
- openIds = ListField(verbose_name = "绑定的用户") # 里面放的是MyUser的Id
- cardName = StringField(verbose_name = "卡名称", default = "")
- ownerOpenId = StringField(verbose_name = "卡的所有者", default = "")
- nickname = StringField(verbose_name = "昵称", default = "")
- logicalCode = StringField(verbose_name = u'发卡的时候记录的设备编码', default = '')
- groupId = StringField(verbose_name = u'发卡的时候记录的地址', default = '')
- dealerId = StringField(verbose_name = "卡的发布老板", default = "")
- groupIds = ListField(verbose_name = "可用使用卡的地址", default = []) # 空表示没有地址,*表示所有地址下可用
- devTypeList = ListField(verbose_name = "设备类型清单", default = [])
- price = MonetaryField(verbose_name = "卡的售价", default = RMB('0.00'))
- periodDays = IntField(verbose_name = "卡的可用天数", default = 30)
- expiredTime = DateTimeField(verbose_name = "过期时间",
- default = lambda: datetime.datetime.now() + datetime.timedelta(days = 365))
- userLimit = IntField(verbose_name = "用户数量限制", default = 10)
- userDesc = StringField(verbose_name = "描述", default = "")
- dayQuota = ListField(verbose_name = "日限额度", default = []) # {'unit':u'次','count':2}
- quota = ListField(verbose_name = "总的额度", default = [])
- frozenQuota = ListField(verbose_name = u'冻结额度', default = [])
- startTime = DateTimeField(verbose_name = "买卡的时间", default = datetime.datetime.now)
- dayUsed = DictField(verbose_name = "日消耗情况", default = {}) # {'2018-12-26':[{'unit':'次','count':3}]}
- quotaUsed = ListField(verbose_name = "总的消耗情况", default = []) # [{'unit':'次','count':3}]
- status = StringField(verbose_name = "卡状态", default = 'normal')
- # expired:过期,耗尽:exhausted, 未激活:nonactivated(经销商开虚拟卡),used(经销商开卡解绑了,并未过期)
- remark = StringField(verbose_name = "卡片备注", default = "")
- continueTime = DateTimeField(verbose_name="买卡的时间", default = None) # 接续时间,用于续费后,下一次卡接续开始
- ongoingList = ListField(DictField(), verbose = u'冻结的配额')
- managerialAppId = StringField(verbose_name = "管理公众号AppId", default = None)
- managerialOpenId = StringField(verbose_name = "管理openId", default = None)
- # 坤元虚拟卡特有:功率
- power = IntField(verbose_name="包月卡功率限制", default=0)
- meta = {
- "collection": "UserVirtualCard",
- "db_alias": "default",
- }
- search_fields = ('nickname', 'cardNo', 'cardName')
- REVERSE_DAY = 3
- @property
- def payVia(self):
- return "virtualCard"
- @staticmethod
- def make_no():
- timestamp = generate_timestamp_ex()
- random_int = random.randint(1, 1000)
- return "%d%03d" % (timestamp, random_int)
- # --------------------------------------------------------------- 所有的虚拟卡的新方法 需要兼容以前 一定需要严格测试
- @staticmethod
- def find_match_unit(quotaList, package):
- """
- 从额度列表中找到符合套餐单位的 返回index
- :param quotaList: [{"unit": "次", "count": 15}, {"unit": "分钟", "count": 600}]
- :param package: {"unit": "分钟", "time": 30}
- """
- # 只有一种额度的 走之前的流程 不要影响客户的使用
- if len(quotaList) == 1:
- return UserVirtualCard._find_match_unit(quotaList, package)
- # 多单位的情况下 不再对单位进行转换 如果单位不匹配 就直接跳过
- pCount, pUnit = package["time"], package["unit"]
- for _index, _quota in enumerate(quotaList):
- if _quota["unit"] != pUnit or float(_quota["count"]) < float(pCount):
- continue
- ii = _index
- break
- else:
- ii = -1
- return ii
- @staticmethod
- def calc_left_quota(quotaList, usedList):
- """
- 计算剩余的额度
- :param quotaList: 总的额度列表
- :param usedList: 已经使用额度情况
- """
- # TODO 目前使用双循环解决,其实可以加一个单位排序,一次遍历搞定 时间复杂度将会降低一个量级
- leftQuotaList = list()
- for _quota in quotaList:
- _qCount, _qUnit = float(_quota["count"]), _quota["unit"]
- for _used in usedList:
- # 单位有匹配的 则直接计算额度
- if _used["unit"] == _qUnit:
- _usedQuota = {"count": float(max(_qCount-_used["count"], 0)), "unit": _qUnit}
- break
- else:
- continue
- # 没有找到这种单位 说明这种额度没有使用过 则剩余额度为满值
- else:
- _usedQuota = {"count": float(_qCount), "unit": _qUnit}
- leftQuotaList.append(_usedQuota)
- return leftQuotaList
- def calc_left_total_quota(self):
- """
- 计算剩余的总额度 将所有的冻结额度都算上使用的
- :return:
- """
- usedMap, frozenMap = dict(), dict()
- for _item in self.quotaUsed:
- if _item["unit"] in usedMap:
- usedMap[_item["unit"]] += float(_item["count"])
- else:
- usedMap[_item["unit"]] = float(_item["count"])
- for _on in self.ongoingList:
- if _on["quota"]["unit"] in frozenMap:
- frozenMap[_on["quota"]["unit"]] += float(_on["quota"]["count"])
- else:
- frozenMap[_on["quota"]["unit"]] = float(_on["quota"]["count"])
- quotaList = self.quota
- usedList = [{"count": _v, "unit": _k} for _k, _v in dict(Counter(usedMap)+Counter(frozenMap)).items()]
- return self.calc_left_quota(quotaList, usedList)
- def calc_left_day_quota(self, dayKey):
- """
- 计算当天剩下的总额度 注意key的匹配 将所有的当天的额度都算上使用的
- :param dayKey:
- :return:
- """
- usedMap, frozenMap = dict(), dict()
- for _item in self.dayUsed.get(dayKey, list()):
- if _item["unit"] in usedMap:
- usedMap[_item["unit"]] += _item["count"]
- else:
- usedMap[_item["unit"]] = _item["count"]
- for _on in self.ongoingList:
- if _on["dayKey"] != dayKey:
- continue
- if _on["dayQuota"]["unit"] in frozenMap:
- frozenMap[_on["dayQuota"]["unit"]] += _on["dayQuota"]["count"]
- else:
- frozenMap[_on["dayQuota"]["unit"]] = _on["dayQuota"]["count"]
- quotaList = self.dayQuota
- usedList = [{"count": _v, "unit": _k} for _k, _v in dict(Counter(usedMap) + Counter(frozenMap)).items()]
- return self.calc_left_quota(quotaList, usedList)
- # --------------------------------------------------------------- 新方法结束
- @staticmethod
- def _find_match_unit(quotaList, package):
- # type:(VirtualCardQuotaList, dict)->int
- match = False
- ii = 0
- for t1Dict in quotaList:
- unit = t1Dict['unit']
- count = t1Dict['count']
- if unit == u'次' and count >= 1:
- match = True
- break
- elif (unit in [u'分钟', u'小时', u'天']) and (package['unit'] in [u'分钟', u'小时', u'天']):
- if unit == u'小时':
- t1Count = count * 60
- elif unit == u'天':
- t1Count = count * 60 * 24
- else:
- t1Count = count
- if package['unit'] == u'小时':
- pCount = float(package['time']) * 60
- elif package['unit'] == u'天':
- pCount = float(package['time']) * 60 * 24
- else:
- pCount = float(package['time'])
- if t1Count >= pCount:
- match = True
- break
- elif (unit in [u'分钟', u'小时', u'天']) and package['unit'] == u'度': # 直接按照12小时计算
- if unit == u'小时':
- t1Count = count * 60
- elif unit == u'天':
- t1Count = count * 60 * 24
- else:
- t1Count = count
- if t1Count >= 6.0 * 60: # 6小时打底
- match = True
- break
- elif unit == u'币' and count >= package['coins']:
- match = True
- break
- elif unit == u'度' and package['unit'] == u'度' and count >= package['time']:
- match = True
- break
- elif unit == u'度' and package['unit'] != u'度' and count >= 3.0:
- match = True
- break
- elif package['unit'] == u'次':
- if unit == u'分钟' and count >= 360.0:
- match = True
- break
- elif unit == u'小时' and count >= 6.0:
- match = True
- break
- elif unit == u'天' and count >= 0.25:
- match = True
- break
- elif unit == u'度' and count >= 3.0: # 默认写死3度电,多退少补
- match = True
- break
- elif unit == u'币' and count >= package['coins']:
- match = True
- break
- ii += 1
- return ii if match else -1
- @staticmethod
- def find_match_unit_and_can_use_count(quotaList, package):
- """
- 找出虚拟卡能否使用多少次 某些设备的特殊需求 例如和动
- """
- # 兼容之前的
- if len(quotaList) == 1:
- usage_count = 0
- for t1Dict in quotaList:
- unit = t1Dict['unit']
- count = t1Dict['count']
- if unit == u'次' and count >= 1:
- usage_count = count
- break
- elif (unit in [u'分钟', u'小时', u'天']) and (package['unit'] in [u'分钟', u'小时', u'天']):
- if unit == u'小时':
- t1Count = count * 60
- elif unit == u'天':
- t1Count = count * 60 * 24
- else:
- t1Count = count
- if package['unit'] == u'小时':
- pCount = float(package['time']) * 60
- elif package['unit'] == u'天':
- pCount = float(package['time']) * 60 * 24
- else:
- pCount = float(package['time'])
- if t1Count >= pCount:
- usage_count = t1Count // pCount
- break
- elif (unit in [u'分钟', u'小时', u'天']) and package['unit'] == u'度': # 直接按照12小时计算
- if unit == u'小时':
- t1Count = count * 60
- elif unit == u'天':
- t1Count = count * 60 * 24
- else:
- t1Count = count
- if t1Count >= 6.0 * 60: # 6小时打底
- usage_count = t1Count // 6.0 * 60
- break
- elif unit == u'币' and count >= package['coins']:
- usage_count = count // float(package['coins'])
- break
- elif unit == u'度' and package['unit'] == u'度' and count >= package['time']:
- usage_count = count // float(package['time'])
- break
- elif unit == u'度' and package['unit'] != u'度' and count >= 3.0:
- usage_count = count // 3
- break
- elif package['unit'] == u'次':
- if unit == u'分钟' and count >= 360.0:
- usage_count = count // 360.0
- break
- elif unit == u'小时' and count >= 6.0:
- usage_count = count // 6
- break
- elif unit == u'天' and count >= 0.25:
- usage_count = count // 0.25
- break
- elif unit == u'度' and count >= 3.0: # 默认写死3度电,多退少补
- usage_count = count // 3
- break
- elif unit == u'币' and count >= package['coins']:
- usage_count = count // float(package['coins'])
- break
- return int(usage_count)
- # 两种单位的
- else:
- pCount, pUnit = package["time"], package["unit"]
- for _index, _quota in enumerate(quotaList):
- # 单位不一致 或者数量不够的 直接跳过
- if _quota["unit"] != pUnit or float(_quota["count"]) < float(pCount):
- continue
- # 然后求出count
- if pUnit == u"次":
- return _quota["count"]
- # 时间的 由于单位一样 直接除法
- else:
- return _quota["count"] // pCount
- else:
- return 0
- @staticmethod
- def count_by_unit(unit, package):
- # type:(unicode, dict)->Union[float, int]
- """
- 根据单位,计算消耗量
- 根据套餐计算以及单位,计算需要扣除的指标数量。比如单位是:币,那就直接看套餐多少币,就需要扣除多少币
- :param unit:
- :param package:
- :return:
- """
- if unit == u'次':
- return 1
- elif unit == u'币':
- return package['coins']
- elif unit == u'分钟':
- if package['unit'] == u'分钟':
- return float(package['time'])
- elif package['unit'] == u'小时':
- return float(package['time']) * 60.0
- elif package['unit'] == u'天':
- return float(package['time']) * 60 * 24.0
- else:
- return 6.0 * 60
- elif unit == u'小时':
- if package['unit'] == u'分钟':
- return float(package['time']) / 60.0
- elif package['unit'] == u'小时':
- return float(package['time'])
- elif package['unit'] == u'天':
- return float(package['time']) * 24.0
- else:
- return 6.0
- elif unit == u'天':
- if package['unit'] == u'分钟':
- return float(package['time']) / 360.0
- elif package['unit'] == u'小时':
- return float(package['time']) / 24.0
- elif package['unit'] == u'天':
- return float(package['time'])
- else:
- return 0.25
- elif unit == u'度':
- if package['unit'] == u'度':
- return float(package['time'])
- else:
- return 3.0
- else:
- raise ValueError(u'does not support unit %s' % (unit,))
- def can_use_today(self, package):
- # type:(dict)->bool
- """
- 检查今天是否可以用卡
- :param package:
- :return:
- """
- if package.get("isTempPackage"):
- return False
- # 非正常状态的卡都不让用
- if self.status not in ["normal", "warning"]:
- return False
- # 先计算出总的剩余额度,以及每日的剩余额度
- leftQuota = self.calc_left_total_quota()
- dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
- leftDayQuota = self.calc_left_day_quota(dayKey)
- # 剩余总额度以及剩余日限额度检查没有问题,就直接返回
- if UserVirtualCard.find_match_unit(leftQuota, package) >= 0 and UserVirtualCard.find_match_unit(leftDayQuota, package) >= 0:
- return True
- return False
- def freeze_quota(self, package, transaction_id): # type:(dict, str) -> bool
- """
- 消费一次,包括扣掉总额,增加消费记录
- :param package:
- :param transaction_id: 交易的ID
- :return:
- """
- try:
- # 找出额度单元,然后扣掉
- leftQuota = self.calc_left_total_quota()
- dayKey = today_format_str()
- leftDayQuota = self.calc_left_day_quota(dayKey)
- allIndex = UserVirtualCard.find_match_unit(leftQuota, package)
- dayIndex = UserVirtualCard.find_match_unit(leftDayQuota, package)
- if allIndex < 0 or dayIndex < 0:
- raise InsufficientFundsError(u'虚拟卡配额不足')
- # 先处理总额度
- unit = leftQuota[allIndex]['unit']
- consume = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
- # 处理每日额度
- unit = leftDayQuota[dayIndex]['unit']
- day_consume = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
- query = {'_id': self.id,
- 'ongoingList.transaction_id': {'$ne': transaction_id}}
- update = {
- '$addToSet': {'ongoingList': {
- 'transaction_id': transaction_id,
- 'quota': consume,
- 'dayQuota': day_consume,
- 'dayKey': dayKey
- }}}
- result = self.get_collection().update_one(filter=query, update=update, upsert = False)
- return bool(result.modified_count == 1)
- finally:
- try:
- day3Key = (datetime.datetime.now() - datetime.timedelta(days = self.REVERSE_DAY)).strftime('%Y-%m-%d')
- for dk in self.dayUsed.keys():
- if dk <= day3Key:
- self.dayUsed.pop(dk)
- self.save()
- except Exception as e:
- logger.exception('save user vcard e={}'.format(e))
- def clear_frozen_quota(self, transaction_id, usedTime, spendElec, backCoins):
- """
- 清除冻结的额度,并按照一定的比例退还
- """
- def calc_left_by_unit(count, unit, ut, ue, uc):
- if unit in [u'分钟', u'小时', u'天']:
- if unit == u'分钟':
- ut = int(ut)
- elif unit == u'小时':
- ut = round(ut / 60.0, 2)
- else:
- ut = round(ut / (60.0 * 24.0), 2)
- leftCount = count - ut
- elif unit in [u'度']:
- leftCount = count - ue
- elif unit in [u'次']: # 如果没有用,比如没有插电,一样,不能扣除配额
- if int(ut) == 0 and int(ue) == 0:
- leftCount = count
- else:
- leftCount = 0
- elif unit in [u'币']: # 注意,这个是已经算好的退币数额
- leftCount = uc
- else:
- leftCount = 0
- return max(leftCount, 0)
- frozen_item = None
- for item in self.ongoingList:
- if item['transaction_id'] == transaction_id:
- frozen_item = item
- break
- if not frozen_item:
- logger.debug('not find this frozen item. card = {}, consume id = {}'.format(repr(self), transaction_id))
- return False, None, None
- consumeTotal = frozen_item['quota']
- consumeTotalLeft = calc_left_by_unit(consumeTotal['count'], consumeTotal['unit'], usedTime, spendElec,
- backCoins)
- consumeTotal['count'] -= float(consumeTotalLeft)
- consumeDay = frozen_item['dayQuota']
- consumeDayLeft = calc_left_by_unit(consumeDay['count'], consumeDay['unit'],
- usedTime, spendElec, backCoins)
- consumeDay['count'] -= float(consumeDayLeft)
- day_key = frozen_item['dayKey']
- clean_day_key = (datetime.datetime.now() - datetime.timedelta(days = self.REVERSE_DAY)).strftime('%Y-%m-%d')
- if day_key <= clean_day_key:
- match_quota_used = None
- for item in self.quotaUsed:
- if item['unit'] == consumeTotal['unit']:
- match_quota_used = item
- break
- if not match_quota_used:
- self.quotaUsed.append({'unit': consumeTotal['unit'], 'count': 0})
- self.save()
- query = {
- '_id': self.id,
- 'ongoingList': {'$elemMatch': {'transaction_id': transaction_id}},
- 'quotaUsed.unit': consumeTotal['unit']
- }
- update = {
- '$inc': {
- 'quotaUsed.$.count': consumeTotal['count']
- },
- '$pull': {'ongoingList': {'transaction_id': transaction_id}},
- }
- result = self.get_collection().update_one(
- filter = query,
- update = update,
- upsert = False
- )
- modified = bool(result.modified_count == 1)
- return modified, consumeTotal, consumeDay
- else:
- match_quota_used = None
- for item in self.quotaUsed:
- if item['unit'] == consumeTotal['unit']:
- match_quota_used = item
- break
- if not match_quota_used:
- self.quotaUsed.append({'unit': consumeTotal['unit'], 'count': 0})
- if day_key not in self.dayUsed:
- self.dayUsed[day_key] = []
- match_day_used = None
- for item in self.dayUsed.get(day_key, []):
- if item['unit'] == consumeDay['unit']:
- match_day_used = item
- break
- if not match_day_used:
- self.dayUsed[day_key].append({'unit': consumeDay['unit'], 'count': 0})
- self.save()
- query = {
- '_id': self.id,
- 'ongoingList': {'$elemMatch': {'transaction_id': transaction_id}},
- 'quotaUsed.unit': consumeTotal['unit'],
- 'dayUsed.{}.unit'.format(day_key): consumeDay['unit']
- }
- update = {
- '$inc': {
- 'quotaUsed.$.count': consumeTotal['count'],
- 'dayUsed.{}.$.count'.format(day_key): consumeDay['count']
- },
- '$pull': {'ongoingList': {'transaction_id': transaction_id}},
- }
- result = self.get_collection().update_one(
- filter = query,
- update = update,
- upsert = False)
- modified = bool(result.modified_count == 1)
- self.reload()
- return modified, consumeTotal, consumeDay
- def recover_frozen_quota(self, transaction_id):
- """
- 清除虚拟卡的冻结额度 不退换
- """
- query = {'_id': self.id}
- update = {
- '$pull': {'ongoingList': {'transaction_id': transaction_id}},
- }
- result = self.get_collection().update_one(
- filter = query,
- update = update,
- upsert = False
- )
- return bool(result.modified_count == 1)
- def consume(self, openId, group, dev, package, attachParas, nickname='', orderNo=None):
- """
- 消费一次,包括扣掉总额,增加消费记录
- :param openId:
- :param group:
- :param dev:
- :param package:
- :param attachParas:
- :param nickname:
- :param orderNo: 订单编号
- :return:
- """
- # 找出额度单元,然后扣掉
- leftQuota = self.calc_left_total_quota()
- dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
- leftDayQuota = self.calc_left_day_quota(dayKey)
- allIndex = UserVirtualCard.find_match_unit(leftQuota, package)
- dayIndex = UserVirtualCard.find_match_unit(leftDayQuota, package)
- if allIndex < 0 or dayIndex < 0:
- return None
- # 先处理总额度
- unit = leftQuota[allIndex]['unit']
- consumeData = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
- match = False
- for tDict in self.quotaUsed:
- if tDict['unit'] == unit:
- tDict['count'] = float(tDict['count']) + float(consumeData['count'])
- match = True
- break
- if not match:
- self.quotaUsed.append(consumeData)
- # 处理每日额度
- unit = leftDayQuota[dayIndex]['unit']
- consumeDayData = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
- match = False
- for tDict in self.dayUsed.get(dayKey, []):
- if tDict['unit'] == unit:
- consumeDayData = {'unit': unit, 'count': UserVirtualCard.count_by_unit(unit, package)}
- tDict['count'] = float(tDict['count']) + float(consumeDayData['count'])
- match = True
- break
- if not match:
- self.dayUsed[dayKey] = [consumeDayData]
- # 清理掉3天以前的每日消费情况
- day3Key = (datetime.datetime.now() - datetime.timedelta(days=3)).strftime('%Y-%m-%d')
- for dk in self.dayUsed.keys():
- if dk <= day3Key:
- self.dayUsed.pop(dk)
- try:
- self.save()
- except Exception as e:
- logger.exception('[consume] save user vcard e={}'.format(e))
- return None
- # 增加一条消费记录
- newRcd = VCardConsumeRecord(
- orderNo = orderNo if orderNo is not None else VCardConsumeRecord.make_no(dev.logicalCode),
- openId = openId,
- nickname = nickname,
- cardId = str(self.id),
- dealerId = self.dealerId,
- devNo = dev['devNo'],
- devTypeCode = dev.devTypeCode,
- devTypeName = dev.devTypeName,
- logicalCode = dev['logicalCode'],
- groupId = group['groupId'],
- address = group['address'],
- groupNumber = dev['groupNumber'],
- groupName = group['groupName'],
- attachParas = attachParas,
- consumeData = consumeData,
- consumeDayData = consumeDayData
- )
- try:
- newRcd.save()
- except Exception as e:
- logger.error('[consume] save vcard consume e=%s' % e)
- return None
- return newRcd
- def new_consume_record(self, dev, user, consumeTotal, attachParas=None, orderNo=None):
- newRcd = VCardConsumeRecord(
- orderNo=orderNo if orderNo is not None else VCardConsumeRecord.make_no(dev.logicalCode),
- openId=user.openId,
- nickname=user.nickname,
- cardId=str(self.id),
- dealerId=dev.ownerId,
- devNo=dev.devNo,
- devTypeCode = dev.devTypeCode,
- devTypeName = dev.devTypeName,
- logicalCode=dev.logicalCode,
- groupId=dev.group.groupId,
- address=dev.group.address,
- groupNumber=dev.groupNumber,
- groupName=dev.group.groupName,
- attachParas=attachParas or dict(),
- consumeData=consumeTotal,
- consumeDayData=consumeTotal
- )
- try:
- newRcd.save()
- except Exception as e:
- logger.error('[consume] save vcard consume e=%s' % e)
- return None
- return newRcd
- def refund_quota(self, consumeRcd, usedTime, spendElec, backCoins):
- """
- 将虚拟卡的额度返回 对应的使用方法是consume
- :param consumeRcd:
- :param usedTime: 单位一定是分钟
- :param spendElec: 单位一定是度
- :param backCoins: 单位一定是金币
- :return:
- """
- def calc_left_by_unit(count, unit, ut, ue, uc):
- if unit in [u'分钟', u'小时', u'天']:
- if unit == u'分钟':
- ut = int(ut)
- elif unit == u'小时':
- ut = round(ut / 60.0, 2)
- else:
- ut = round(ut / (60.0 * 24.0), 2)
- leftCount = count - ut
- elif unit in [u'度']:
- leftCount = count - ue
- elif unit in [u'次']: # 如果没有用,比如没有插电,一样,不能扣除配额
- if int(ut) == 0 and int(ue) == 0:
- leftCount = count
- else:
- leftCount = 0
- elif unit in [u'币']: # 注意,这个是已经算好的退币数额
- leftCount = float(VirtualCoin(uc))
- else:
- leftCount = 0
- return leftCount
- # 当消费已经定下来的时候 则本次退换的单位实际上已经固定了
- consumeLeftTemp = calc_left_by_unit(
- consumeRcd.consumeData['count'],
- consumeRcd.consumeData['unit'],
- usedTime,
- spendElec,
- backCoins
- )
- consumeLeft = max(consumeLeftTemp,0)
- consumeRcd.consumeData['count'] -= float(consumeLeft)
- consumeDayLeft = calc_left_by_unit(
- consumeRcd.consumeDayData['count'],
- consumeRcd.consumeDayData['unit'],
- usedTime,
- spendElec,
- backCoins
- )
- consumeRcd.consumeDayData['count'] -= float(consumeDayLeft)
- # 刷新消费记录数据
- try:
- consumeRcd.save()
- except Exception as e:
- logger.exception('[refund_quota] save consume rcd error = %s' % e)
- return
- # 更新额度
- for tDict in self.quotaUsed:
- if tDict['unit'] != consumeRcd.consumeData['unit']:
- continue
- tDict['count'] -= consumeLeft
- dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
- for tDict in self.dayUsed.get(dayKey, []):
- if tDict['unit'] != consumeRcd.consumeDayData['unit']:
- continue
- tDict['count'] -= float(consumeDayLeft)
- try:
- self.save()
- except Exception as e:
- logger.exception('[refund_quota] save quota error=%s' % e)
-
- # 返回本条记录余下的配套
- returnUsedTime,returnSpendElec,returnBackCoins = usedTime,spendElec,backCoins
- if consumeRcd.consumeData['unit'] in [u'分钟',u'小时',u'秒']:
- return consumeLeftTemp,returnSpendElec,returnBackCoins
- if consumeRcd.consumeData['unit'] in [u'度']:
- return returnUsedTime,consumeLeftTemp,returnBackCoins
- return returnUsedTime,returnSpendElec,returnBackCoins
-
- # 卡续费
- def continue_card(self):
- if not self.continueTime:
- return
- vCard = VirtualCard.objects.get(id = self.cardTypeId)
- # 以最新的属性作为 购买
- self.price = vCard.price
- self.dayQuota = vCard.dayQuota
- self.userLimit = vCard.userLimit
- self.cardName = vCard.cardName
- self.quota = vCard.quota
- self.userDesc = vCard.userDesc
- self.dayUsed = {}
- self.quotaUsed = []
- self.status = 'normal'
- self.expiredTime = self.continueTime + datetime.timedelta(seconds=vCard.periodDays * 24 * 3600)
- self.continueTime = None
- self.save()
- return self
- @staticmethod
- def get_user_cards(openId, groupId, ownerId):
- """
- 获取用户所有的虚拟卡
- 修改: 去掉continueTime的check判断 没意义
- """
- cardList = []
- vCards = UserVirtualCard.objects.filter(
- dealerId = ownerId, openIds = openId,
- groupIds__in = ['*', groupId]
- )
- nowTime = datetime.datetime.now()
- for obj in vCards:
- if obj.startTime <= nowTime <= obj.expiredTime:
- cardList.append(obj)
- return cardList
- def add_expired_time(self, days):
- self.expiredTime += datetime.timedelta(days = int(days))
- self.save()
- def support_dev_type(self, devTypeCode):
- devTypesCount = DeviceType.objects.filter(code = devTypeCode, id__in = self.devTypeList).count()
- if devTypesCount <= 0:
- return False
- return True
- @property
- def groupInfo(self):
- """
- 获取虚拟卡的相应的 地址信息
- :return:
- """
- if '*' in self.groupIds:
- groups = [{'groupName': grp['groupName'], 'address': grp['address']} for grp in
- Group.get_groups_by_group_ids(Group.get_group_ids_of_dealer(self.dealerId)).values()]
- else:
- groups = [{'groupName': grp['groupName'], 'address': grp['address']} for grp in
- Group.get_groups_by_group_ids(self.groupIds).values()]
- return groups
- @property
- def membersInfo(self):
- """
- 虚拟卡成员信息
- :return:
- """
- users = MyUser.objects.filter(openId__in = self.openIds).only("openId", "nickname")
- bindUsersList = list()
- repeatUsers = set()
- for user in users:
- if user.openId in repeatUsers:
- continue
- # openId 相同去重
- repeatUsers.add(user.openId)
- tempDict = {
- 'userName': user.nickname,
- 'userId': user.openId
- }
- user.openId == self.ownerOpenId and tempDict.update({"role": "owner"})
- bindUsersList.append(tempDict)
- return bindUsersList
- @property
- def quotaInfo(self):
- """
- 获取和额度相关的信息
- :return:
- """
- dayUsedList = self.dayUsed.get(datetime.datetime.now().strftime("%Y-%m-%d"), list())
- dayUsed = 0
- if len(dayUsedList) > 0:
- dayUsed = dayUsedList[0]['count']
- return {
- 'quota': self.quota[0]['count'],
- 'quotaUnit': self.quota[0]['unit'],
- 'quotaUsed': round(self.quotaUsed[0]['count'], 2) if len(self.quotaUsed) > 0 else 0,
- 'dayQuota': self.dayQuota[0]['count'],
- 'dayQuotaUnit': self.dayQuota[0]['unit'],
- 'dayUsed': round(dayUsed, 2)
- }
- @property
- def devTypes(self):
- devs = DeviceType.objects.filter(id__in=self.devTypeList).only("majorDeviceType", "name")
- devTypes = list()
- for _dev in devs: # type: DeviceType
- devTypes.append({"devTypeName": _dev.name, "majorDeviceType": _dev.majorDeviceType})
- return devTypes
- def isOwner(self, openId):
- """是不是卡的所有者"""
- return openId == self.ownerOpenId
- def to_dict(self):
- """
- 序列化
- :return:
- """
- data = {
- 'cardId': str(self.id),
- 'cardNo': self.cardNo,
- 'cardTypeId': self.cardTypeId,
- 'cardName': self.cardName,
- 'periodDays': self.periodDays,
- 'createTime': self.startTime.strftime('%Y-%m-%d'),
- 'expiredTime': self.expiredTime.strftime('%Y-%m-%d'),
- 'userDesc': self.userDesc,
- 'userLimit': self.userLimit,
- 'userName': self.nickname,
- 'logicalCode': self.logicalCode,
- 'groupId': self.groupId,
- 'remark': self.remark,
- 'power': self.power
- }
- return data
- def to_detail(self):
- data = {
- 'cardId': str(self.id),
- 'cardNo': self.cardNo,
- 'cardTypeId': self.cardTypeId,
- 'cardName': self.cardName,
- 'periodDays': self.periodDays,
- 'createTime': self.startTime.strftime('%Y-%m-%d'),
- 'expiredTime': self.expiredTime.strftime('%Y-%m-%d'),
- 'userDesc': self.userDesc,
- 'userLimit': self.userLimit,
- 'userName': self.nickname,
- 'logicalCode': self.logicalCode,
- 'groupId': self.groupId,
- 'remark': self.remark,
- "groups": self.groupInfo,
- "sharedMembers": self.membersInfo,
- "devTypes": self.devTypes,
- # 功率
- 'power': self.power,
- # 额度
- 'quota': {
- "day": self.dayQuota,
- "total": self.quota
- },
- # 剩余的额度
- 'leftQuota': {
- "day": self.calc_left_day_quota(today_format_str()),
- "total": self.calc_left_total_quota()
- }
- }
- return data
- # 以下是霍坡的特殊业务 之前是不支持按次 现在支持了 考虑业务合并
- def can_use_hp_gate(self):
- """霍珀虚拟卡 对于道闸计费 只要卡没过期就可以使用"""
- # 尚未过期的虚拟卡可用于霍珀道闸
- # 要求变更 虚拟卡过期后还能在使用24小时
- if self.status not in ["normal", "warning"]:
- return False
- return self.expiredTime > datetime.datetime.now() - datetime.timedelta(hours = 24)
- def consume_hp_gate(self, openId, group, dev, package = None, attachParas={}, nickname=''):
- # type:(str, GroupDict, DeviceDict, dict, dict, str)->Optional[VCardConsumeRecord]
- """
- 消费一次
- :param openId:
- :param group:
- :param dev:
- :param package:
- :param attachParas:
- :param nickname:
- :return:
- """
- # 增加一条消费记录
- newRcd = VCardConsumeRecord(
- orderNo = VCardConsumeRecord.make_no(dev.logicalCode),
- openId = openId,
- nickname = nickname,
- cardId = str(self.id),
- dealerId = self.dealerId,
- devNo = dev['devNo'],
- devTypeCode = dev.devTypeCode,
- devTypeName = dev.devTypeName,
- logicalCode = dev['logicalCode'],
- groupId = group['groupId'],
- address = group['address'],
- groupNumber = dev['groupNumber'],
- groupName = group['groupName'],
- attachParas = attachParas,
- remarks = u"道闸刷卡服务"
- )
- try:
- newRcd.save()
- self.record_gate_used_times()
- except Exception as e:
- logger.error('save vcard consume e=%s' % e)
- return None
- return newRcd
- def record_gate_used_times(self):
- """
- 霍珀要求的 对于道闸次数进行监控,但是不能限制次数
- :return:
- """
- dayKey = datetime.datetime.now().strftime("%Y-%m-%d")
- cacheKey = "{}-{}".format(dayKey, self.cardNo)
- times = serviceCache.get(cacheKey, 0)
- times += 1
- serviceCache.set(cacheKey, times, timeout = 60 * 60 * 24)
- if times < 8:
- status = "normal"
- else:
- status = "warning"
- try:
- self.update(status = status)
- except Exception as e:
- logger.exception(e)
- return
- class VirtualCardRechargeRecord(Searchable):
- orderNo = StringField(verbose_name = u'使用RechargeRecord的orderNo关联两张表', required = True)
- cardId = StringField(verbose_name = "虚拟卡ID", default = "")
- cardNo = StringField(verbose_name = "虚拟卡卡号", default = "")
- openId = StringField(verbose_name = "openId", default = "")
- gateway = StringField(verbose_name = 'gateway', default = "")
- groupId = StringField(verbose_name = "设备地址编号", default = "")
- ownerId = ObjectIdField(verbose_name = "dealerId")
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '生成时间')
- # 以下字段后续废弃, 只做兼容使用
- devNo = StringField(verbose_name = "设备ID", default = None)
- logicalCode = StringField(verbose_name = "设备逻辑编码", default = None)
- wxOrderNo = StringField(verbose_name = "orderNo", default = None)
- money = MonetaryField(verbose_name = "付款金额", default = None)
- coins = MonetaryField(verbose_name = "充值金额", default = None)
- devTypeCode = StringField(verbose_name = "设备类型编码", default = None)
- time = StringField(verbose_name = 'time', default = None)
- devType = StringField(verbose_name = "设备类型", default = None)
- address = StringField(verbose_name = "设备地址", default = None)
- groupNumber = StringField(verbose_name = "设备", default = None)
- groupName = StringField(verbose_name = "交易场地", default = None)
- remarks = StringField(verbose_name = "备注", default = None)
- status = StringField(verbose_name = "状态", default = None)
- meta = {
- "collection": "virtualCardRechargeRecord",
- "db_alias": "default",
- }
- @classmethod
- def get_link_orderNo_list(cls, cardId, startTime, endTime, openId = None):
- records = cls.objects(
- cardId = cardId, dateTimeAdded__gte = startTime, dateTimeAdded__lt = get_tomorrow_zero_time(endTime))
- if openId:
- records = records.filter(openId = openId)
- return [record.orderNo for record in records]
- @property
- def created_date(self):
- return self.dateTimeAdded
- def is_refund_available(self, customer): # type:(CapitalUser) -> bool
- return False
- @property
- def has_refund_order(self):
- return False
- @property
- def isQuickPay(self):
- return False
- @classmethod
- def issue(cls, order):
- # type:(RechargeRecord)->VirtualCardRechargeRecord
- return cls(
- orderNo = order.orderNo,
- cardId = order.attachParas['cardId'],
- cardNo = order.attachParas['cardNo'],
- openId = order.openId,
- ownerId = order.ownerId,
- groupId = order.groupId,
- gateway = order.gateway,
- dateTimeAdded = order.dateTimeAdded).save()
- # 监督号的用户,用于和实际系统的用户进行关联
- class MoniUser(Searchable):
- moniAppId = StringField(verbose_name = 'moniAppId', default = '')
- moniOpenId = StringField(verbose_name = 'moniOpenId', default = '')
- openId = StringField(verbose_name = u'用户的主APPID', default = '')
- isSubscribe = BooleanField(verbose_name = u'是否订阅', default = False)
- checkTime = DateTimeField(verbose_name = u'检查时间')
- subTime = DateTimeField(verbose_name = u'关注时间')
- unsubTime = DateTimeField(verbose_name = u'取关时间')
- subLogicalCode = StringField(verbose_name = u'关注来源logicalCode', default = '')
- subDealerId = StringField(verbose_name = u'关注来源dealerId', default = '')
- subAgentId = StringField(verbose_name = u'关注来源agentId', default = '')
- subPoint = StringField(verbose_name = u'关注来源point', default = '')
- meta = {
- "collection": "moni_user",
- "db_alias": "logdata",
- }
- @classmethod
- def get_or_create(cls, moniOpenId, moniAppId, **kwargs):
- """
- 创建一条用户的记录
- """
- obj = cls.objects.filter(moniOpenId=moniOpenId, moniAppId=moniAppId).order_by("-id").first() or cls(moniOpenId=moniOpenId, moniAppId=moniAppId)
- needUpdate = False
- for _field in cls._fields.keys():
- if _field in kwargs and kwargs[_field] != getattr(obj, _field, kwargs[_field]):
- setattr(obj, _field, kwargs[_field])
- needUpdate = True
- if needUpdate:
- obj = obj.save()
- return obj
- @classmethod
- def get_or_delete(cls, moniOpenId, moniAppId):
- cls.objects.filter(moniOpenId=moniOpenId, moniAppId=moniAppId).update(isSubscribe=False)
- class AskStep(Searchable):
- operKey = StringField(verbose_name = 'oper key', default = '')
- answerText = StringField(verbose_name = 'answer text', default = '')
- answerPics = StringField(verbose_name = 'answer picture', default = '') # '文件名称用,分割'
- answerFunc = StringField(verbose_name = 'answer func', default = '')
- @staticmethod
- def replyUseDevice(moniOpenId, appId, **kwargs):
- moniUser = MoniUser.objects.filter(moniOpenId = moniOpenId, moniAppId = appId).first()
- if moniUser:
- devLink = concat_user_login_entry_url(l=moniUser.subLogicalCode)
- return [{'msgType': 'text', 'value': u'\ue231<a href="%s">戳我使用设备</a>\ue230' % devLink}]
- return [{'msgType': 'text', 'value': u'您直接点击菜单栏的扫一扫,或者关闭当前页面重新扫码,即可使用'}]
- @staticmethod
- def replayQueryBalance(moniOpenId, appId, **kwargs):
- openId = kwargs.get('openId')
- agentId = kwargs.get('agentId')
- users = MyUser.objects.filter(openId = openId)
- balance = VirtualCoin(0.0)
- for user in users:
- balance += user.balance
- url = concat_user_center_entry_url(agentId=agentId,
- redirect=concat_front_end_url(uri='/user/index.html#/user/help'))
- return [{'msgType': 'text',
- 'value': u'您在系统总的余额是%s,如果界面上显示不对,请您先刷新页面,还是不行的话,<a href="%s">联系客服</a>' % (balance, url)}]
- @staticmethod
- def get_service_phone_from_rcd(consumeRcd):
- dealer = Dealer.objects(id=consumeRcd.ownerId).first() # type: Dealer
- return dealer.service_phone
- @staticmethod
- def replyGetToushuUrl(moniOpenId, appId, **kwargs):
- agentId = kwargs.get('agentId')
- url = concat_user_center_entry_url(agentId=agentId,
- redirect=concat_front_end_url(uri='/user/index.html#/complaint/list'))
- return [{'msgType': 'text', 'value': u'<a href="%s">投诉点这里</a>' % url}]
- # 先检查用户的订单是否扣掉了钱。然后检查设备的连通性。
- # 调用此函数的时候,一般是用户付款了,设备没有响应。
- @staticmethod
- def replyQueryConsumeRcd(moniOpenId, appId, **kwargs):
- try:
- endTime = datetime.datetime.now()
- startTime = endTime - datetime.timedelta(days = 7)
- consumeRcd = ConsumeRecord.objects.filter(openId = kwargs['openId'], dateTimeAdded__gte = startTime,
- dateTimeAdded__lte = endTime).order_by('-dateTimeAdded').first()
- agentId = kwargs.get('agentId')
- url = concat_user_center_entry_url(agentId=agentId,
- redirect=concat_front_end_url(uri='/user/index.html#/user/feedbackList'))
- if consumeRcd is None:
- return [
- {'msgType': 'text',
- 'value': u'系统中没有查到您最近7天的使用记录/::-|,这种情况比较少见,建议您先创建一张<a href="%s">报告单</a>,然后拨打客服电话,让客服尽快处理' % url},
- {'msgType': 'image', 'value': '1.png'},
- {'msgType': 'image', 'value': '2.png'},
- {'msgType': 'image', 'value': '3.png'},
- {'msgType': 'image', 'value': '4.png'},
- ]
- rcdDesc = u'地址:%s,编号:%s,付款:%s,时间:%s' % (consumeRcd.address, consumeRcd.logicalCode, consumeRcd.coin,
- consumeRcd.dateTimeAdded.strftime(Const.DATETIME_FMT))
- if consumeRcd.isNormal:
- return [
- {'msgType': 'text', 'value': u'查到您最新的订单,信息如下:%s' % rcdDesc},
- {'msgType': 'text',
- 'value': u'钱确实用出去了/:--b,设备如果没有正常响应或者故障,您先拍照留下证据。然后您在服务&投诉中,<a href="%s">报告老板</a>,申请上分或者退币。然后在常见问题菜单中,拨打客服电话,让客服尽快处理/::)。如果没有及时响应,您也可以先行自己补款,然后记得向设备老板申请退币/:rose' % url}
- ]
- else:
- return [
- {'msgType': 'text', 'value': u'查到您最新的订单,信息如下:%s' % rcdDesc},
- {'msgType': 'text', 'value': u'亲,刚查询到您确实有个消费订单,没有成功/:--b。系统已经自动给您退币了,您可以重新扫码尝试启动设备,或者换台别的设备试试吧/::)'}
- ]
- except Exception, e:
- return [{'msgType': 'text',
- 'value': u'我在系统中没有查到您最近的使用记录/::<,这种情况,建议您在服务&投诉中<a href="%s">报告老板</a>,申请上分或者退币。然后在常见问题中,拨打客服电话,让客服尽快处理/::P' % url}]
- @staticmethod
- def replyQueryServicedPhone(moniOpenId, appId, **kwargs):
- agentId = kwargs.get('agentId')
- endTime = datetime.datetime.now()
- startTime = endTime - datetime.timedelta(days = 7)
- consumeRcd = ConsumeRecord.objects.filter(openId = kwargs['openId'], dateTimeAdded__gte = startTime,
- dateTimeAdded__lte = endTime).order_by('-dateTimeAdded').first()
- if consumeRcd:
- phone = AskStep.get_service_phone_from_rcd(consumeRcd)
- return [{'msgType': 'text', 'value': u'/::P这个是客服电话吧:%s' % phone}]
- else:
- url = concat_user_center_entry_url(agentId=agentId,
- redirect=concat_front_end_url(uri='/user/index.html#/user/help'))
- return [{'msgType': 'text', 'value': u'这个给您,<a href="%s">客服电话</a>' % url}]
- def reply(self, moniOpenId, appId, **kwargs):
- if self.answerFunc:
- return eval('AskStep.%s(moniOpenId,appId,**kwargs)' % self.answerFunc)
- elif self.answerText:
- return [{'msgType': 'text', 'value': self.answerText}]
- elif self.answerPics:
- return [{'msgType': 'image', 'value': pic} for pic in self.answerPics]
- else:
- return [{'msgType': 'text', 'value': ''}]
- # 聊天模式匹配库。用于批量智能客服(不需要那么复杂的聊天,而且也贵;可以针对性进行数据库查询,更灵活)
- class AskRobot(Searchable):
- ask = StringField(verbose_name = 'ask', default = '') # 问题模式,是可以匹配的: 机器/洗衣机/设备/,不启动/没反应/不动/不转/没动静
- steps = ListField(verbose_name = 'answer steps', default = []) # 支持
- priority = IntField(verbose_name = 'priority', default = 1)
- askList = []
- @staticmethod
- def load_in_mem_if_not_exist():
- # if AskRobot.askList:#调试比较麻烦,直接查询所有吧,以后做了界面,再打开
- # return
- objs = AskRobot.objects.all().order_by('-priority')
- for obj in objs:
- AskRobot.askList.append(obj)
- @staticmethod
- def match_sentence(ask, sentence, score):
- wordList = sentence.split('/')
- for word in wordList:
- if word in ask:
- logger.info('it is match,sentence=%s,ask=%s' % (sentence, ask))
- return score
- return 0.0
- # 根据图片文件名称找ID,文章标题找ID
- @staticmethod
- def find_material_id(proxy, mediaType, value):
- if mediaType == 'image':
- results = proxy.client.material.batchget(media_type = 'image', offset = 0,
- count = 1000) # 简单粗暴,目前看素材不会超过20个
- for item in results.get('item', []):
- if item['name'] == value:
- return item['media_id']
- return None
- return None
- @staticmethod
- def reply(event, moniOpenId, ask, appId, secret, **kwargs):
- AskRobot.load_in_mem_if_not_exist()
- # 产生一个线程,让线程后台处理这个客服消息
- class replyer(threading.Thread):
- def __init__(self, event, moniOpenId, ask, appId, secret, **kwargs):
- super(replyer, self).__init__()
- self._event = event
- self._moniOpenId = moniOpenId
- self._ask = ask
- self._appId = appId
- self._secret = secret
- self._kwargs = kwargs
- def run(self):
- try:
- replys = []
- # 如果是关注事件,直接推送文章
- if self._event == 'subscribe':
- materialId = MoniApp.objects.get(appId = self._appId).welcomeArticleMaterialId
- replys.append({'msgType': 'news', 'value': materialId})
- moniUser = MoniUser.objects.filter(moniOpenId = self._moniOpenId,
- moniAppId = self._appId).first()
- if moniUser:
- devLink = concat_user_login_entry_url(l=moniUser.subLogicalCode)
- replys.append({'msgType': 'text',
- 'value': u'/:rose亲,欢迎您关注自助设备服务平台!乐乐可以为您解决售后问题以及投诉事宜/:hug,——————— \ue231<a href="%s">戳我使用设备</a>\ue230' % devLink})
- else:
- # 找到所有问题的答案
- for askConfig in AskRobot.askList:
- sentenceList = askConfig.ask.split(',')
- sentenceScore = 100.0 / len(sentenceList)
- allScore = 0
- for sentence in sentenceList:
- allScore += AskRobot.match_sentence(self._ask, sentence, sentenceScore)
- if allScore >= 90:
- logger.info('find match answer key = %s' % ','.join(askConfig.steps))
- replys = askConfig.answer(self._moniOpenId, self._appId, **self._kwargs)
- break
- logger.info('reply len %s' % len(replys))
- # 把答案的文章、图片发给用户
- app = WechatAuthApp(appid = self._appId, secret = self._secret)
- proxy = WechatClientProxy(app)
- for reply in replys:
- if not reply:
- continue
- try:
- if reply['msgType'] == 'text' and reply['value']:
- proxy.client.message.send_text(moniOpenId, reply['value'])
- elif reply['msgType'] == 'image' and reply['value']:
- materialId = AskRobot.find_material_id(proxy, 'image', reply['value'])
- proxy.client.message.send_image(moniOpenId, materialId)
- elif reply['msgType'] == 'news' and reply['value']:
- proxy.client.message.send_articles(moniOpenId, reply['value'])
- time.sleep(1)
- except Exception, e:
- logger.exception('send msg to user failed = %s' % (e))
- continue
- except Exception as e:
- logger.info('replyer error,e=%s' % e)
- sender = replyer(event, moniOpenId, ask, appId, secret, **kwargs)
- sender.start()
- def answer(self, moniOpenId, appId, **kwargs):
- # 首先把当前回话的历史信息拉出来
- historyReplys = serviceCache.get(moniOpenId, '')
- replyList = []
- for stepKey in self.steps:
- step = AskStep.objects.get(operKey = stepKey)
- sameReplyCount = historyReplys.count(stepKey)
- if sameReplyCount == 0:
- reply = step.reply(moniOpenId, appId, **kwargs)
- elif sameReplyCount == 1:
- if 'xiangyin' not in stepKey:
- reply = [{'msgType': 'text', 'value': u'建议您试试我给的建议哦/::P'}].extend(
- step.reply(moniOpenId, appId, **kwargs))
- else:
- reply = [{'msgType': 'text', 'value': u'嗯,您的问题是什么呢?/::P'}]
- elif sameReplyCount == 2:
- step = AskStep.objects.get(operKey = 'kefudianhua')
- reply = step.reply(moniOpenId, appId, **kwargs)
- else:
- reply = [{'msgType': 'text', 'value': ''}]
- if reply:
- historyReplys += '%s,' % stepKey
- serviceCache.set(moniOpenId, historyReplys, 600)
- replyList.extend(reply)
- return replyList
- class WxAuthTransfer(Searchable):
- agentId = StringField(verbose_name = u'代理商ID', default = '')
- oldAppId = StringField(verbose_name = u'老的appid', default = '')
- oldOpenId = StringField(verbose_name = u'老的openid', default = '')
- newAppId = StringField(verbose_name = u'新的appid', default = '')
- newOpenId = StringField(verbose_name = u'新的openid', default = '')
- # subAgentList = ListField(verbose_name = '子agent列表', default = '')
- firstPhase = BooleanField(verbose_name = u'第1阶段任务完成', default = False)
- secondPhase = BooleanField(verbose_name = u'第2阶段任务完成', default = False)
- meta = {
- "collection": "wx_auth_transfer",
- "db_alias": "logdata"
- }
- def __repr__(self):
- return '{}<agent={}, old=({},{}), new=({},{})>'.format(
- self.__class__.__name__,
- str(self.agentId),
- self.oldAppId, self.oldOpenId,
- self.newAppId, self.newOpenId)
- class ICChargeOperating(Searchable):
- """
- deprecated 该功能废弃, 保证脚本能正常进行
- """
- cardId = StringField(verbose_name = 'cardId', default = '')
- balance = MonetaryField(verbose_name = u"卡余额", default = RMB('0.00'))
- needChargeMoney = MonetaryField(verbose_name = u"需要充值的钱", default = RMB('0.00'))
- dateTimeAdded = DateTimeField(verbose_name = u'操作时间', default = datetime.datetime.now)
- meta = {
- "collection": "ic_charge_operating",
- "db_alias": "logdata"
- }
- class BlackListUsers(Searchable):
- class Status(IterConstant):
- BLACK = "black"
- WHITE = 'white'
- openId = StringField(verbose_name = "被拉黑用户OpenId")
- dealerId = StringField(verbose_name = "经销商ID")
- agentId = StringField(verbose_name = "代理商ID")
- reason = StringField(verbose_name = "被拉黑的理由", default = "")
- operator = StringField(verbose_name = "操作人")
- status = StringField(verbose_name = "拉黑状态", choices = Status.choices(), default = Status.BLACK)
- dateTimeAdded = DateTimeField(verbose_name = "拉黑进去的时间", default = datetime.datetime.now)
- meta = {
- "db_alias": "default",
- 'indexes': [
- 'dealerId',
- ],
- }
- @classmethod
- def add_black_user(cls, openId, dealerId, operator, reason = ""):
- """
- 拉黑用户
- :param openId:
- :param dealerId:
- :param operator:
- :param reason:
- :return:
- """
- try:
- record = cls.objects.get(dealerId = dealerId, openId = openId)
- except DoesNotExist:
- dealer = Dealer.get_dealer(dealerId) or dict()
- record = cls(
- openId = openId,
- dealerId = dealerId,
- agentId = dealer.get("agentId", ""),
- operator = operator,
- reason = reason
- ).save()
- record.update(status = cls.Status.BLACK)
- @classmethod
- def freed_black_user(cls, openId, dealerId):
- """
- 解除用户的拉黑状态 这个地方不删除记录 防止之后要有需求对拉黑用户进行天数限制
- :param openId:
- :param dealerId:
- :return:
- """
- try:
- record = cls.objects.get(dealerId = dealerId, openId = openId)
- except DoesNotExist:
- return
- record.update(status = cls.Status.WHITE)
- @classmethod
- def check_black(cls, openId, dealerId):
- """
- 检查该用户是否被拉黑
- :param openId:
- :param dealerId:
- :return:
- """
- record = cls.objects(openId=openId, dealerId__in=[dealerId, '*']).first()
- if not record:
- return False
- if record.status == cls.Status.WHITE:
- return False
- else:
- return True
- # 此表用于解决复制卡的问题。每张卡都有一个变化的随机码,每次扣费后会新生成一个随机码。每次扣钱后,会上报上次的随机码
- # 如果随机码出现一次重复,说明出现一次复制卡。随机码是刷卡器生成的时间标签,恰好重复的可能性微乎其微。
- class WeifuleCardStamp(Searchable):
- cardNo = StringField(verbose_name = "卡号", default = "")
- dealerId = StringField(verbose_name = "经销商ID", default = "")
- dateTimeAdded = DateTimeField(verbose_name = '添加时间', default = datetime.datetime.now())
- stamp = StringField(verbose_name = 'stamp', default = "")
- meta = {
- "collection": "weifule_card_stamp",
- "db_alias": "default"
- }
- @staticmethod
- def is_copy_card(cardNo, dealerId, stamp):
- harfYear = datetime.datetime.now() - datetime.timedelta(days = 6 * 30)
- copyCount = WeifuleCardStamp.objects.filter(cardNo = cardNo, dealerId = dealerId, dateTimeAdded__gte = harfYear,
- stamp = stamp).count()
- if copyCount:
- return True
- return False
- class SwapCardRecord(Searchable):
- oldCardNo = StringField(verbose_name = u"旧卡卡号")
- newCardNo = StringField(verbose_name = u"新卡卡号")
- agentId = StringField(verbose_name = u"补卡的代理商ID")
- operator = StringField(verbose_name = u"操作人")
- dateTimeAdded = DateTimeField(verbose_name = u"补卡时间", default = datetime.datetime.now)
- meta = {'collection': 'SwapCardRecord', 'db_alias': 'logdata'}
- @classmethod
- def add_record(cls, oldCardNo, newCardNo, agentId, operator):
- """
- 添加一条补卡记录
- :param oldCardNo: 旧卡卡号
- :param newCardNo: 新卡卡号
- :param agentId: 补卡代理商ID
- :param operator: 操作者ID
- :return:
- """
- record = cls(
- newCardNo = newCardNo,
- oldCardNo = oldCardNo,
- agentId = agentId,
- operator = operator
- )
- record.save()
- return record
- class DuibijiOrderMap(Searchable):
- devOrderId = IntField(verbose_name = 'device order id', unique = True)
- consumeRcdId = StringField(verbose_name = 'consume rcd id', unique = True)
- rechargeRcdId = StringField(verbose_name = 'rechargeRcdId id', default = '')
- status = StringField(verbose_name = 'status', default = '')
- openId = StringField(verbose_name = 'openId', default = '')
- dateTimeAdded = DateTimeField(verbose_name = 'dateTimeAdded', default = datetime.datetime.now())
- class MonthlyPackageUseInfo(EmbeddedDocument):
- orderNo = StringField(verbose_name = u"使用订单编号", required = True)
- orderTime = DateTimeField(verbose_name = u"使用的时间", required = True)
- def to_dict(self):
- return {"orderNo": self.orderNo, "orderTime": self.orderTime}
- class MonthlyPackage(Searchable):
- # 用户相关信息
- name = StringField(verbose_name = u"包月套餐的名称", default = "")
- groupId = StringField(verbose_name = u"包月绑定的地址组", required = True)
- openId = StringField(verbose_name = u"用户的唯一ID", required = True)
- cardNo = StringField(verbose_name = u"刷卡能够使用的卡号", default = "")
- bothCardAndMobile = BooleanIntField(verbose_name = u"是否是通用的(扫码和刷卡通用)", default = False)
- # 包月相关信息
- startDateTime = DateTimeField(verbose_name = u"有效期起始时间", required = True)
- expireDateTime = DateTimeField(verbose_name = u"过期的时间", required = True)
- maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 0, min_value = 0)
- maxCount = IntField(verbose_name = u"可供使用的最大次数", default = 0, min_value = 0)
- maxTimeOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 0, min_value = 0)
- maxElecOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 0, min_value = 0)
- # 基本信息
- isDisable = BooleanIntField(verbose_name = u"是否已经失效", default = 0)
- usedTotal = IntField(verbose_name = u"使用的总次数", default = 0)
- usedDetail = MapField(verbose_name = u"用户使用情况",
- field = EmbeddedDocumentListField(document_type = MonthlyPackageUseInfo))
- dateTimeAdded = DateTimeField(verbose_name = u"购买的时间 一般与起始时间开始", default = datetime.datetime.now)
- @property
- def payVia(self):
- return "monthlyPackage"
- @staticmethod
- def format_date(date): # type: (datetime.date) -> str
- """
- :param date:
- :return:
- """
- return "T{}".format(date.strftime("%Y_%m_%d"))
- @classmethod
- def get_enable_one(cls, openId, groupId, cardNo = ""):
- """
- 找包月的 openId groupId 一定需要对应上 并且 允许通用 或者不允许通用但是卡号不为空
- :return:
- """
- group = Group.get_group(groupId) # type: GroupDict
- dealer = Dealer.objects.get(id = group.ownerId)
- groupIds = Dealer.get_currency_group_ids(dealer, group)
- groupIds.append(groupId)
- objs = cls.objects.filter(
- openId = openId, groupId__in = groupIds, isDisable = 0
- ).filter(
- Q(cardNo = cardNo) | Q(bothCardAndMobile = 1)
- ).order_by('dateTimeAdded')
- result = filter(
- lambda obj: obj.useable,
- map(lambda obj: obj.disable(), objs)
- )
- return result[0] if result else None
- @classmethod
- def get_user_all(cls, openId):
- """
- 获取用户所有的包月套餐规则
- :param openId:
- :return:
- """
- return cls.objects.filter(openId = openId, isDisable = 0)
- @classmethod
- def create_by_template(cls, template, rechargeOrder): # type:(MonthlyPackageTemp, RechargeRecord) -> MonthlyPackage
- subIndex = rechargeOrder.attachParas.get("subTempIndex")
- purchasedMonth = template.subTemplate[subIndex].numOfMonth
- data = {
- "name": template.subTemplate[subIndex].displayName,
- "groupId": rechargeOrder.groupId,
- "openId": rechargeOrder.openId,
- "cardNo": rechargeOrder.attachParas.get("cardNo", ""),
- "bothCardAndMobile": template.bothCardAndMobile,
- "startDateTime": rechargeOrder.dateTimeAdded,
- "expireDateTime": rechargeOrder.dateTimeAdded + relativedelta.relativedelta(months = purchasedMonth),
- "maxCountOfDay": template.maxCountOfDay,
- "maxCount": template.maxCountOfMonth * purchasedMonth,
- "maxTimeOfCount": template.maxTimeOfCount,
- "maxElecOfCount": template.maxElecOfCount,
- }
- obj = cls(**data)
- return obj.save()
- @property
- def useable(self):
- """
- :return:
- """
- if self.isDisable:
- return False
- # 总次数
- if len(self.usedDetail) >= self.maxCount:
- return False
- # 0 表示不限制
- if self.maxCountOfDay == 0:
- return True
- else:
- # 当日的次数超限
- dateStr = self.format_date(datetime.date.today())
- dateUseInfo = self.usedDetail.get(dateStr, list())
- if len(dateUseInfo) >= self.maxCountOfDay:
- return False
- return True
- def deduct(self, order):
- # type:(ConsumeRecord)->None
- """
- 使用一次, 扣除一次额度, 根据订单进行扣除
- :return:
- """
- orderTime = order.dateTimeAdded # type: datetime.datetime
- orderInfo = MonthlyPackageUseInfo(orderNo = order.orderNo, orderTime = orderTime)
- updateData = {
- "inc__usedTotal": 1,
- "add_to_set__usedDetail__{}".format(self.format_date(orderTime)): orderInfo
- }
- return self.update(**updateData)
- def rollback(self, order):
- """
- 用于抵扣回退 目前存在与5分钟以内 需要全退的情况
- """
- orderTime = order.dateTimeAdded # type: datetime.datetime
- orderInfo = MonthlyPackageUseInfo(orderNo=order.orderNo, orderTime=orderTime)
- updateData = {
- "dec__usedTotal": 1,
- "pull__usedDetail__{}".format(self.format_date(orderTime)): orderInfo
- }
- return self.update(**updateData)
- def disable(self):
- """
- 检查将 过期的包月套餐直接清理掉
- :return:
- """
- if self.expireDateTime <= datetime.datetime.now():
- self.update(isDisable = 1)
- return self.reload()
- return self
- def is_suit_package(self, package):
- """
- 判断当前的包月套餐是否试用用户选择的套餐
- 时间和电量的 检验是否足够抵扣 单次的直接抵扣一次 其余的暂不支持
- :param package:
- :return:
- """
- if package.get("unit") == u"分钟":
- unit = "Time"
- elif package.get("unit") == u"度":
- unit = "Elec"
- elif package.get("unit") == u"次":
- return True
- else:
- return False
- # 这个地方默认的规则时 如果没有适配到合适的套餐 就直接判定为不能够使用
- count = getattr(self, "max{}OfCount".format(unit), 0)
- # 添加一种情况(当count为0时候) 不做限制,只用次数
- if count == 0:
- return True
- if count >= int(package.get("time", 0xFFFF)):
- return True
- return False
- def to_dict(self):
- dayUse = len(self.usedDetail.get(self.format_date(date = datetime.date.today()), list()))
- return {
- "id": self.id,
- "name": self.name,
- "expireDateTime": self.expireDateTime.strftime("%Y-%m-%d %H:%M:%S"),
- "dayLeft": self.maxCountOfDay - dayUse,
- "totalLeft": self.maxCount - self.usedTotal,
- "maxElecOfCount": self.maxElecOfCount,
- "maxTimeOfCount": self.maxTimeOfCount,
- }
- def get_used_detail(self):
- usedDetail = list()
- for _k, _v in self.usedDetail.items():
- usedDetail.extend([_m.to_dict() for _m in _v])
- usedDetail.sort(key = lambda x: x["orderTime"])
- return usedDetail
- @classmethod
- def get_user_ticket(cls, openId, groupId, cardNo = ""):
- group = Group.get_group(groupId) # type: GroupDict
- dealer = Dealer.objects.get(id = group.ownerId)
- groupIds = Dealer.get_currency_group_ids(dealer, group)
- groupIds.append(groupId)
- objs = cls.objects.filter(
- openId = openId, groupId__in = groupIds, isDisable = 0
- ).filter(
- Q(cardNo = cardNo) | Q(bothCardAndMobile = 1)
- ).order_by('dateTimeAdded')
- return map(lambda obj: obj.disable(), objs)
- @staticmethod
- def get_can_use_one(all_tickets, package):
- # type: (List[MonthlyPackage], dict) -> (MonthlyPackage)
- for ticket in all_tickets:
- if ticket.useable and ticket.is_suit_package(package):
- return ticket
- return None
- class Redpack(Searchable):
- # 红包信息
- # TODO 建立索引 factoryCode(索引), openId(索引)
- # 红包活动类型
- class RedpackType():
- LAXIN = 'laxin'
- RUHUI = 'ruhui'
- class Result(IterConstant):
- FINISHED = 'finished'
- PROCESSING = 'processing'
- openId = StringField(verbose_name='openId', default='')
- title = StringField(verbose_name='红包标题', default='')
- factoryCode = StringField(verbose_name='TaskId')
- redpackType = StringField(verbose_name='红包类型', default='')
- # 红包自身流程
- money = MonetaryField(verbose_name='红包金额', default=RMB('0.00'))
- leastPayMoney = MonetaryField(verbose_name='最小使用金额', default=RMB('0.00'))
- effectTime = DateTimeField(verbose_name='红包生效时间')
- expiredTime = DateTimeField(verbose_name='红包过期时间')
- usedStatus = BooleanField(verbose_name='是否使用', default=False)
- usedTime = DateTimeField(verbose_name='使用时间')
- consumeRecordId = StringField(verbose_name='关联消费订单')
- package = DictField(verbose_name='选中的套餐')
- # 来源信息
- gateway = StringField(verbose_name='平台', default='')
- logicalCode = StringField(verbose_name='设备号', default='')
- devNo = StringField(verbose_name='IMEI', default='')
- dateTimeAdded = DateTimeField(verbose_name='记录添加时间', default=datetime.datetime.now)
- extra = DictField(verbose_name='其他', default={})
- taskStatus = StringField(verbose_name="任务当前状态", default=Result.PROCESSING)
- showType = StringField(verbose_name="红包展示方式")
- meta = {
- "db_alias": "default"
- }
- # 红包业务流程
- @classmethod
- def pre_deducted_coins(cls, redpackId, package):
- # 预计抵扣的金额计算, 此函数目前只用于蓝牙流程
- redpack = cls.objects.filter(id=redpackId, usedStatus=False).first()
- if not redpack:
- return 0.0
- if redpack.money > RMB(package['price']):
- return round(RMB(package['coins']), 2)
- else:
- ratio = Ratio(float(package['coins']) / float(package['price']))
- return round(RMB(redpack.money) * ratio, 2)
- @property
- def to_deducted_coins(self):
- if not self.package:
- return 0.0
- if self.money > RMB(self.package['price']):
- return round(RMB(self.package['coins']), 2)
- else:
- ratio = Ratio(float(self.package['coins']) / float(self.package['price']))
- return round(RMB(self.money) * ratio, 2)
- @property
- def to_deducted_money(self):
- if not self.package:
- return self.money
- else:
- if self.money > RMB(self.package['price']):
- return round(RMB(self.package['price']), 2)
- else:
- return round(RMB(self.money), 2)
- @staticmethod
- def use_redpack(redPacketId, consumeRecordId, package):
- # type: (str, str, dict) -> int
- # 1 校验红包(过期, 状态)
- nowTime = datetime.datetime.now()
- return Redpack.objects.filter(id=redPacketId, usedStatus=False).update(usedStatus=True, usedTime=nowTime,
- consumeRecordId=consumeRecordId, package=package)
- @classmethod
- def get_redpack_list(cls, filtetr, rmb=None):
- """
- rmb 当前使用金额, 如果没有的话
- 例: 红包 满2 - 0.2 最小使用金额必须
- """
- nowTime = datetime.datetime.now()
- if not rmb:
- rmb = RMB(0).mongo_amount
- exp_time = nowTime + datetime.timedelta(minutes=30) # 给30分钟时间给设备启动留用
- redPackets = cls.objects.filter(
- effectTime__lte=nowTime,
- expiredTime__gte=exp_time,
- leastPayMoney__lte=rmb,
- usedStatus=False,
- taskStatus=cls.Result.FINISHED,
- **filtetr
- ).order_by('expiredTime')
- if not redPackets:
- return []
- return list(map(lambda x: x.to_dict(), redPackets))
- def to_dict(self):
- return {
- 'openId': self.openId,
- 'title': self.title,
- 'factoryCode': self.factoryCode,
- 'money': RMB(self.money).mongo_amount,
- 'leastPayMoney': RMB(self.leastPayMoney).mongo_amount,
- 'effectTime': self.effectTime,
- 'expiredTime': self.expiredTime,
- 'usedStatus': self.usedStatus,
- 'logicalCode': self.logicalCode,
- 'consumeRecordId': self.consumeRecordId,
- 'redpackCoins': self.to_deducted_coins,
- 'redpackMoney': self.to_deducted_money,
- 'id': str(self.id),
- 'taskStatus': self.taskStatus,
- 'showType': self.showType,
- }
- def rollback_redpack(self, redPacketId):
- redPacket = self.objects.filter(id=redPacketId).first()
- if not Redpack:
- return False
- return redPacket.update(usedStatus=False)
- @classmethod
- def can_use(cls, dealer, devTypeCode):
- # type: (Dealer, str)->bool
- dealer_features = map(lambda x: x['key'], filter(lambda x: x['value'] == True, dealer.feature_list))
- if 'disable_redpack' in dealer_features:
- return False
- else:
- if devTypeCode and devTypeCode in Redpack.list_of_devices_that_support_redpack():
- return True
- return False
- @classmethod
- def auto_suit_with_money(cls, query, rmb):
- """
- 自动挑选合适的红包抵扣支付金额
- # 有两种判断:
- 1 红包金额大于支付金额, 优先选择与红包金额接近金额支付的红包使用
- 2 红包金额小于支付金额, 优先选择大的红包金额
- """
- try:
- redpacks = cls.get_redpack_list(query)
- if redpacks:
- # 第一种情况做筛选 红包金额大于支付金额, 优先选择与红包金额接近金额支付的红包使用
- _enough_list = filter(lambda _: RMB(_.get('money')) >= rmb, redpacks)
- if _enough_list:
- return min(_enough_list, key=lambda _: RMB(_.get('money', 0)))
- # 第二种情况 没有足够支付的金额 选取最大的红包进行抵扣
- return max(redpacks, key=lambda _: RMB(_.get('money', 0)))
- except:
- import traceback
- logger.error(traceback.format_exc())
- return
- @classmethod
- def auto_suit_with_coins(cls, query, userBalance, package):
- """
- 自动挑选合适的红包抵扣金币
- # 有三种判断:
- 1 红包足以 抵扣 挑选最小的红包
- 2 红包 + 金币 抵扣 挑选最小的红包
- 2 返回 都不足以抵扣 返回空
- """
- try:
- package_money = RMB(package.get('price', 0))
- if package_money == RMB(0):
- return
- package_coins = RMB(package.get('coins', 0))
- ratio = Ratio(float(package.get('coins', 0.0)) / float(package.get('price', 0.0)))
- redpacks = cls.get_redpack_list(query)
- if redpacks:
- # 第一种情况 红包足以 抵扣 挑选最小的红包
- _enough_list = filter(lambda _: RMB(_.get('money')) >= package_money, redpacks)
- if _enough_list:
- return min(_enough_list, key=lambda _: RMB(_.get('money', 0)))
- # 第二种情况 红包 + 金币 抵扣 挑选最小的红包
- _mix_enough_list = filter(lambda _: RMB(_.get('money', 0)) * ratio + RMB(userBalance) >= package_coins, redpacks)
- if _mix_enough_list:
- return min(_mix_enough_list, key=lambda _: RMB(_.get('money', 0)))
- except:
- import traceback
- logger.error(traceback.format_exc())
- return
- @classmethod
- def get_one(self, redpackId):
- redpack = Redpack.objects.filter(id=redpackId).first() # type: Redpack
- if not redpack:
- return {}
- else:
- return redpack.to_dict()
- @classmethod
- def show_redpack(cls, openId):
- nowTime = datetime.datetime.now()
- exp_time = nowTime - datetime.timedelta(days=3)
- # 筛选出过期时间超过三天或者 使用后时间超过三天的红包
- redPackets = cls.objects.filter(Q(expiredTime__gte=exp_time) | Q(usedTime__gte=exp_time),
- openId=openId, taskStatus=cls.Result.FINISHED).order_by('-expiredTime')
- if not redPackets:
- return []
- return list(map(lambda x: x.to_dict(), redPackets))
- @staticmethod
- def list_of_devices_that_support_redpack():
- from apps.web.core.models import SystemSettings
- support_redpack_list = SystemSettings.get_support_redpack_list()
- return support_redpack_list
- @staticmethod
- def add_device_to_support_redpack_list(devTypeCode):
- from apps.web.core.models import SystemSettings
- support_redpack_list = SystemSettings.get_support_redpack_list()
- support_redpack_list.append(devTypeCode)
- support_redpack_list = list(set(support_redpack_list))
- SystemSettings.set_support_redpack_list(support_redpack_list)
- logger.info('Current support redpck list:{}'.format(support_redpack_list))
- @staticmethod
- def remove_device_to_support_redpack_list(devTypeCode):
- from apps.web.core.models import SystemSettings
- support_redpack_list = SystemSettings.get_support_redpack_list()
- if devTypeCode in support_redpack_list:
- support_redpack_list.remove(devTypeCode)
- support_redpack_list = list(set(support_redpack_list))
- SystemSettings.set_support_redpack_list(support_redpack_list)
- logger.info('Current support redpck list:{}'.format(support_redpack_list))
- @staticmethod
- def renew_support_repack_list(devTypeCodeList):
- from apps.web.core.models import SystemSettings
- SystemSettings.set_support_redpack_list(devTypeCodeList)
- # 拉新创建红包*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
- @staticmethod
- def create_redpack_by_laxin(factoryCode, openId, money, leastPayMoney, effectTime, expiredTime, gateway,
- logicalCode, devNo, extra):
- if Redpack.objects.filter(factoryCode=factoryCode, openId=openId).first():
- return
- else:
- red_packet = {
- 'factoryCode': factoryCode,
- 'title': '支付宝每日任务红包',
- 'openId': openId,
- 'money': money,
- 'leastPayMoney': leastPayMoney,
- 'effectTime': effectTime,
- 'expiredTime': expiredTime,
- 'gateway': gateway,
- 'logicalCode': logicalCode,
- 'devNo': devNo,
- 'extra': extra,
- 'taskStatus': Redpack.Result.FINISHED,
- 'redpackType': Redpack.RedpackType.LAXIN
- }
- try:
- redpack = Redpack.objects.create(**red_packet)
- except:
- return None
- return redpack
- # 入会创建红包*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
- @staticmethod
- def create_redpack_by_ruhui(taskId, openId, urlId, money, leastPayMoney, gateway, logicalCode, devNo, extra, showType=''):
- redpack = Redpack.objects.filter(factoryCode=taskId, openId=openId, redpackType=Redpack.RedpackType.RUHUI,
- taskStatus__ne=Redpack.Result.FINISHED).first()
- if redpack:
- redpack.update(**{
- 'logicalCode': logicalCode,
- 'devNo': devNo,
- 'extra': extra,
- 'taskStatus': Redpack.Result.PROCESSING,
- 'expiredTime': datetime.datetime.now() + datetime.timedelta(days=7),
- 'urlId': urlId,
- 'dateTimeAdded': datetime.datetime.now(),
- 'showType':showType,
- })
- else:
- red_packet = {
- 'factoryCode': taskId,
- 'title': '支付宝每日任务红包.',
- 'openId': openId,
- 'money': money,
- 'leastPayMoney': leastPayMoney,
- 'gateway': gateway,
- 'logicalCode': logicalCode,
- 'devNo': devNo,
- 'extra': extra,
- 'taskStatus': Redpack.Result.PROCESSING,
- 'expiredTime': datetime.datetime.now() + datetime.timedelta(days=7),
- 'redpackType': Redpack.RedpackType.RUHUI,
- 'urlId': urlId,
- 'showType': showType,
- }
- try:
- redpack = Redpack.objects.create(**red_packet)
- except:
- return
- return redpack
- @staticmethod
- def take_effect(openId, urlId, **kw):
- try:
- obj = Redpack.objects.filter(openId=openId, urlId=urlId, redpackType=Redpack.RedpackType.RUHUI,
- taskStatus__ne=Redpack.Result.FINISHED).first()
- if not obj:
- return
- extra = obj.extra
- extra.update(kw)
- obj.update(
- extra=extra,
- taskStatus=Redpack.Result.FINISHED,
- effectTime=datetime.datetime.now(),
- expiredTime=datetime.datetime.now() + datetime.timedelta(days=15)
- )
- return obj.reload()
- except:
- import traceback
- logger.error(traceback.format_exc())
- class OneCardGateLog(Searchable):
- devNo = StringField(verbose_name=u"设备编号")
- logicalCode = StringField(verbose_name=u"逻辑编号")
- openId = StringField(verbose_name=u"用户信息")
- control = IntField(verbose_name=u"进出标记")
- result = BooleanField(verbose_name=u"操纵结果")
- dateTimeAdded = DateTimeField(verbose_name=u"添加时间")
- meta = {
- 'collection': 'OneCardGateLog',
- 'db_alias': 'logdata'
- }
- # ------------------------------------ 以下为新增 ------------------------
- class OrderPackage(dict):
- def belong_category(self, category): # type: (str) -> bool
- return self.category == category
- @property
- def policyType(self): # type: () -> Optional[str, None]
- return self.get("policyType") or self.get("category")
- @property
- def category(self): # type: () -> str
- """
- 套餐的种类 实际上也是相应的启动方式
- """
- if self.policyType:
- return self.policyType
- unit = self.get("unit")
- if unit in [u"度"]:
- return PackageCategory.ELEC
- if unit in [u"元"]:
- return PackageCategory.COIN
- if unit in [u"小时", u"分钟"]:
- return PackageCategory.TIME
- return "unknown"
- @property
- def isPostpaid(self):
- return self.get("isPostpaid", False)
- @property
- def autoRefund(self):
- return self.get("autoRefund", False)
- @property
- def autoStop(self):
- return self.get("autoStop", False)
- @property
- def minFee(self):
- return RMB(self.get("minFee", 0))
- @property
- def minAfterStartCoins(self):
- return RMB(self.get("minAfterStartCoins", 0))
- @property
- def isFree(self):
- return self.get("isFree", False)
- @property
- def price(self):
- """
- 套餐的价格 后付费 即先用后付的情况 显示价格为0
- """
- if self.isFree or self.isPostpaid:
- return RMB(0)
- return RMB(self.get("price", 0))
- @property
- def time(self): # type:()->int
- """
- 套餐的时间单位 最终为 分钟
- 注意区分是否已经初始化过了
- """
- value = self.get("m_time")
- if value:
- return value
- # 初始化过程
- value = self.get('time', 0) if self.belong_category(PackageCategory.TIME) else 0
- if self.get("unit") == u"小时":
- return int(float(value) * 60)
- if self.get("unit") == u"分钟":
- return int(value)
- return 0
- @property
- def elec(self):
- """
- 套餐的电量单位 最终为 度
- """
- value = self.get("m_elec")
- if value:
- return value
- return float(self.get('time', 0) if self.belong_category(PackageCategory.ELEC) else 0)
- @property
- def coin(self):
- """
- 注意 这个coin不是价格的意思 指的是直接是设备运行的硬币数 例如投币 或者某些直接以金额启动设备的套餐
- """
- value = self.get("m_coin")
- if value:
- return value
- return int(self.get('time', 0) if self.belong_category(PackageCategory.COIN) else 0)
- @property
- def name(self):
- return self.get("name", "")
- @property
- def desc(self):
- return self.get("desc", "")
- @property
- def rules(self):
- """
- 即计费规则
- """
- return self.get("rules") or None
- @property
- def refundProtectTime(self):
- return self.get("refundProtectTime", 0)
- @property
- def dumpDict(self):
- """
- 需要被订单固化的数据
- """
- return {
- "category": self.category,
- "name": self.name,
- "price": self.price.mongo_amount,
- "m_time": self.time,
- "m_elec": self.elec,
- "m_coin": self.coin,
- "minAfterStartCoins": self.minAfterStartCoins.mongo_amount,
- "minFee": self.minFee.mongo_amount,
- "autoRefund": self.autoRefund,
- "autoStop": self.autoStop,
- "rules": self.rules,
- "isFree": self.isFree,
- "isPostpaid": self.isPostpaid,
- "desc": self.desc,
- "refundProtectTime": self.refundProtectTime
- }
- @property
- def showDict(self):
- return {
- "category": self.category,
- "name": self.name,
- "time": self.time,
- "elec": self.elec,
- "coin": self.coin,
- "minAfterStartCoins": self.minAfterStartCoins.mongo_amount,
- "minFee": self.minFee.mongo_amount,
- "autoRefund": self.autoRefund,
- "autoStop": self.autoStop,
- "rules": self.rules,
- "isFree": self.isFree,
- "isPostpaid": self.isPostpaid,
- "desc": self.desc,
- "refundProtectTime": self.refundProtectTime
- }
- class PaymentInfo(dict):
- @property
- def deduct_list(self):
- return self.get("deduct_list") or list()
- @property
- def time(self):
- return self.get("time")
- @time.setter
- def time(self, value):
- self.update({"time": value})
- @property
- def isPaid(self):
- return self.time is not None
- @property
- def via(self):
- return self.get("via")
- @property
- def totalAmount(self):
- s = RMB(0)
- for _item in self.deduct_list:
- s += RMB(_item.get("chargeBalance"))
- s += RMB(_item.get("bestowBalance"))
- return s
- @property
- def actualAmount(self):
- """
- 排除赠送余额之后的支付金额
- """
- s = RMB(0)
- for _item in self.deduct_list:
- s += RMB(_item.get("chargeBalance"))
- return s
- @property
- def bestowAmount(self):
- s = RMB(0)
- for _item in self.deduct_list:
- s += RMB(_item.get("bestowBalance"))
- return s
- class ServiceInfo(dict):
- """
- 和设备运行的一切相关信息
- """
- @property
- def deviceStartTime(self):
- return self.get(ConsumeOrderServiceItem.START_TIME)
- @deviceStartTime.setter
- def deviceStartTime(self, value):
- if isinstance(value, datetime.datetime):
- value = value.strftime("%Y-%m-%d %H:%M:%S")
- self.update({ConsumeOrderServiceItem.START_TIME: value})
- @property
- def elec(self):
- return self.get(ConsumeOrderServiceItem.ELEC)
- @elec.setter
- def elec(self, value):
- self.update({ConsumeOrderServiceItem.ELEC: float(value)})
- @property
- def duration(self):
- return self.get(ConsumeOrderServiceItem.DURATION)
- @duration.setter
- def duration(self, value):
- self.update({ConsumeOrderServiceItem.DURATION: int(value)})
- @property
- def deviceEndTime(self):
- return self.get(ConsumeOrderServiceItem.END_TIME)
- @deviceEndTime.setter
- def deviceEndTime(self, value):
- if isinstance(value, datetime.datetime):
- value = value.strftime("%Y-%m-%d %H:%M:%S")
- self.update({ConsumeOrderServiceItem.END_TIME: value})
- @property
- def maxPower(self):
- return self.get(ConsumeOrderServiceItem.MAX_POWER)
- @maxPower.setter
- def maxPower(self, value):
- self.update({ConsumeOrderServiceItem.MAX_POWER: value})
- @property
- def reason(self):
- return self.get(ConsumeOrderServiceItem.REASON)
- @reason.setter
- def reason(self, value):
- self.update({ConsumeOrderServiceItem.REASON: value})
- @property
- def spendMoney(self):
- return RMB(self.get(ConsumeOrderServiceItem.SPEND, 0))
- @spendMoney.setter
- def spendMoney(self, value):
- self.update({ConsumeOrderServiceItem.SPEND: RMB(value)})
- class ConsumeRecord(Searchable):
- """
- 用户的消费订单记录
- """
- class Status(IterConstant):
- CREATED = 'created' # 订单创建初始状态 刚刚下单
- WAIT_CONF = 'waitConf' # 用户下完单之后【等待】用户进一步的动作确认
- FINISHED = 'finished' # 订单的结束状态 表示相应信息已经被记录
- WAITING = 'waiting' # 订单的执行等待状态 设备尚未执行该订单
- RUNNING = 'running' # 订单执行运行状态 设备正在执行该订单
- END = "end" # 订单运行结束状态 设备已经执行订单完毕 订单完结
- TIMEOUT = 'timeout' # 订单启动超时状态 设备启动超时 有可能已经启动了
- FAILURE = 'failure' # 订单执行失败状态 设备明确启动失败 订单已经完结
- UNKNOWN = 'unknown' # 订单的未知状态 未知的订单状态
- WAIT_PAY = 'waitPay' # 订单支付的中间状态
- orderNo = StringField(verbose_name=u"订单号", required=True, unique=True)
- sequenceNo = StringField(verbose_name=u"流水号", unique=True)
- logicalCode = StringField(verbose_name=u"设备编号", required=True)
- groupId = StringField(verbose_name=u"设备地址编号", default=None)
- price = MonetaryField(verbose_name=u"订单价格", default=RMB('0'))
- startType = IntField(verbose_name=u"启动方式", choices=StartDeviceType.choices())
- status = StringField(verbose_name=u"状态", default=Status.CREATED)
- ownerId = StringField(verbose_name=u"经销商", required=True)
- remarks = StringField(verbose_name=u"备注(系统、用户)", default='')
- description = StringField(verbose_name=u"订单描述(错误信息)")
- association = DictField(verbose_name=u'关联单', default={})
- serviceInfo = DictField(verbose_name=u"订单的服务信息", default={})
- isFree = BooleanField(verbose_name=u"是否订单免费", default=False)
- dateTimeAdded = DateTimeField(verbose_name=u"创建时间", default=datetime.datetime.now)
- finishedTime = DateTimeField(verbose_name=u"结束时间")
- cardId = StringField(verbose_name=u"卡ID")
- openId = StringField(verbose_name=u"用户", required=True)
- nickname = StringField(verbose_name=u'用户昵称', default='')
- # 以下信息属于订单快照 防止引用被修改
- devNo = StringField(verbose_name=u"设备ID", required=True)
- port = IntField(verbose_name=u"启动端口")
- devTypeName = StringField(verbose_name=u"设备类型名称", default=None)
- devTypeCode = StringField(verbose_name=u"设备类型编码", default=None)
- address = StringField(verbose_name=u"设备地址", default=None)
- groupNumber = StringField(verbose_name=u"设备", default=None)
- groupName = StringField(verbose_name=u"交易场地", default=None)
- # 退款状态应该是用户下单的时候即被锁定 和设备、支付、套餐有关系
- startPackage = DictField(verbose_name=u'启动套餐', required=True)
- paymentInfo = DictField(verbose_name=u'支付的相关信息', default={})
- refundInfo = DictField(verbose_name=u"退还的相关信息", default={})
- dailyStats = LazyReferenceField(verbose_name="统计关联单", document_type='DealerGroupStats')
- feedbackId = ObjectIdField(verbose_name=u'用户反馈ID')
- search_fields = ('openId', 'devNo', 'orderNo', 'remarks', 'logicalCode')
- _shard_key = ('ownerId', 'dateTimeAdded')
- _origin_meta = {
- "collection": "ConsumeRecord",
- "db_alias": "default"
- }
- meta = _origin_meta
- def __str__(self):
- return '{}<id={} orderNo={}>'.format(self.__class__.__name__, str(self.id), self.orderNo)
- @cached_property
- def owner(self):
- # 防止id报错
- if not self.ownerId:
- return None
- from apps.web.dealer.models import Dealer
- dealer = Dealer.objects(id=self.ownerId).first()
- return dealer
- @cached_property
- def user(self): # type:() -> MyUser
- return MyUser.objects.filter(openId=self.openId, groupId=self.groupId).first()
- @cached_property
- def card(self):
- return Card.objects.filter(id=self.cardId).first()
- @property
- def coin(self):
- return self.price
- @property
- def package(self): # type: () -> OrderPackage
- return OrderPackage(self.startPackage)
- @property
- def device(self): # type:()-> DeviceDict
- return Device.get_dev(self.devNo)
- @property
- def group(self): # type:() -> GroupDict
- return Group.get_group(self.groupId)
- @property
- def startLockKey(self):
- return "{}-{}-start-device-lock".format(self.openId, self.orderNo)
- @property
- def isPayTimeOut(self):
- """
- 订单到支付下单的一个过程
- """
- now = datetime.datetime.now()
- return (now - self.dateTimeAdded).total_seconds() > CONSUME_ORDER_PAY_TIMEOUT
- @property
- def isPostPaid(self):
- """
- True 后付费订单 使用完给钱
- False 预付费订单 先给钱再使用然后再决定是否退款
- """
- return self.package.isPostpaid
- @property
- def isStartNetPay(self):
- return self.startType == StartDeviceType.ON_LIEN
- @property
- def isStartCardPay(self):
- return self.startType == StartDeviceType.CARD
- @property
- def payer(self):
- if self.startType == StartDeviceType.ON_LIEN:
- return self.user
- else:
- return self.card
- @property
- def payment(self): # type: () -> PaymentInfo
- return PaymentInfo(self.paymentInfo)
- @property
- def refund(self): # type: () -> PaymentInfo
- return PaymentInfo(self.refundInfo)
- @property
- def service(self): # type:() -> ServiceInfo
- return ServiceInfo(self.serviceInfo)
- @property
- def device_start_time(self):
- return self.service.deviceStartTime
- @property
- def device_end_time(self):
- return self.service.deviceEndTime
- @property
- def isPaid(self):
- if self.isFree:
- return True
- return self.payment.isPaid
- @property
- def actualAmount(self):
- """
- 用户订单实际消费的金额
- """
- return self.payment.actualAmount - self.refund.actualAmount
- @property
- def detail_link(self):
- if self.device.deviceAdapter.support_count_down(self.openId, self.port):
- return concat_count_down_page_url(devNo=self.devNo, port=self.port)
- else:
- return concat_front_end_url(uri='/user/index.html#/user/consumeDetail?id={}'.format(str(self.id)))
- @property
- def receiptDesc(self):
- rv = {
- 'orderNo': self.orderNo,
- 'createdTime': self.dateTimeAdded,
- 'payment': u'{} 元'.format(self.price)
- }
- if self.isFree:
- rv.update({"payment": u"本次免费"})
- if self.isPostPaid:
- rv.update({"payment": u"先用后付"})
- return rv
- @property
- def startKey(self):
- return self.orderNo
- @property
- def actualAmount(self):
- return self.payment.actualAmount - self.refund.actualAmount
- @property
- def bestowAmount(self):
- return self.payment.bestowAmount - self.refund.bestowAmount
- @property
- def isNormal(self):
- """
- 是否为正常订单
- """
- if self.status in [self.Status.FAILURE, self.Status.UNKNOWN, self.Status.TIMEOUT]:
- return False
- if self.status in [self.Status.WAIT_CONF, self.Status.CREATED]:
- return False
- return True
- @property
- def device_identity_info(self):
- return {
- 'logicalCode': self.logicalCode,
- 'devTypeCode': self.devTypeCode,
- 'devTypeName': self.devTypeName,
- 'groupName': self.groupName,
- 'groupNumber': self.groupNumber,
- 'address': self.address,
- 'groupId': self.groupId
- }
- @classmethod
- def make_no(cls, *args, **kwargs):
- """
- 有可能会重复 需要保证唯一性
- """
- timestamp = generate_timestamp_ex()
- random_int = random.randint(1, 1000)
- return "%d%03d" % (timestamp, random_int)
- @classmethod
- def new_one(cls, orderNo, user, device, context): # type:(str, MyUser, DeviceDict, StartParamContext) -> ConsumeRecord
- order = cls(
- orderNo=orderNo,
- sequenceNo=context.sequence,
- logicalCode=device.logicalCode,
- groupId=device.groupId,
- price=context.package.price,
- startType=context.startType,
- ownerId=device.ownerId,
- openId=user.openId,
- cardId=context.cardId,
- nickname=user.nickname,
- devNo=device.devNo,
- port=context.port,
- devTypeName=device.devTypeName,
- devTypeCode=device.devTypeCode,
- address=device.group.address,
- groupNumber=device.groupNumber,
- groupName=device.group.groupName,
- startPackage=context.package.dumpDict,
- isFree=context.package.isFree,
- autoRefund=context.package.autoRefund
- )
- return order.save()
- def update_payment(self, payment): # type:(dict) -> bool
- """支付信息添加"""
- if self.isPaid:
- return False
- self.paymentInfo = payment
- return self.save()
- def frozen_payer_balance(self):
- """
- 执行订单的支付过程 实际上是将用户的金额冻结
- """
- if not self.payment:
- return
- if self.isPaid:
- return
- payer, payment = self.payer, self.payment
- result = payer.__class__.freeze_balance(str(self.id), self.payment)
- if not result:
- return
- # 更新一次付款信息
- payment.time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- self.update_payment(payment)
- payer.account_consume(self)
- def clear_payer_frozen(self, refundMoney=RMB(0)):
- from apps.web.user.utils2 import generate_refund
- refundInfo = PaymentInfo(generate_refund(self, refundMoney))
- result = self.payer.__class__.clear_frozen_balance(
- str(self.id),
- self.payment,
- refundInfo
- )
- if not result:
- return
- if refundMoney == RMB(0):
- return
- refundInfo.time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- self.refundInfo = refundInfo
- self.save()
- self.payer.account_refund(self)
- def link_state(self, state):
- self.dailyStats = state
- self.finishedTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- self.save()
- def to_user_detail(self):
- data = {
- "id": str(self.id),
- "orderNo": self.orderNo,
- "ownerId": self.ownerId,
- "money": self.price,
- "createdTime": self.dateTimeAdded,
- "completionTime": self.finishedTime,
- "startResult": True if self.device_start_time else False,
- "errorDesc": self.description,
- "deviceStatTime": self.device_start_time,
- "deviceFinishedTime": self.device_end_time,
- "isRefund": True if self.refund.time else False,
- "orderStatus": self.status
- }
- if self.refund.time:
- data.update({
- "refundedMoney": self.refund.totalAmount
- })
- data.update(self.device_identity_info)
- return data
- def to_detail(self):
- data = {
- "id": str(self.id),
- 'orderNo': self.orderNo,
- 'createdTime': self.dateTimeAdded.strftime("%Y-%m-%d %H:%M:%S"),
- 'completionTime': self.finishedTime.strftime("%Y-%m-%d %H:%M:%S") if self.finishedTime else "",
- 'deviceStatTime': self.device_start_time,
- 'deviceFinishedTime': self.device_end_time,
- 'amount': self.price.amount,
- 'openId': self.openId,
- 'groupId': self.groupId,
- 'userNickname': u'用户' if not self.nickname else self.nickname,
- 'ownerId': self.ownerId,
- 'devNo': self.devNo,
- 'logicalCode': self.logicalCode,
- 'groupName': self.groupName,
- 'address': self.address,
- 'groupNumber': self.groupNumber,
- 'devType': self.devTypeName, # 兼容
- 'devTypeName': self.devTypeName,
- 'remarks': self.remarks,
- 'startResult': 'success' if self.isNormal else 'failed',
- 'errorDesc': self.description,
- 'servicedInfo': [
- u'%s: %s'.encode('utf-8') % (GLOSSARY_TRANSLATION.get(k, k), v) for k, v in self.service.iteritems()
- ],
- # 以下字段和以前做兼容
- 'finishedTime': self.device_end_time,
- 'port': self.port
- }
- return data
- @cached_property
- def statusText(self):
- if self.status == self.Status.CREATED:
- return u"订单创建"
- elif self.status == self.Status.WAIT_CONF:
- return u"等待支付"
- elif self.status == self.Status.FINISHED:
- return u"订单结束"
- elif self.status == self.Status.WAITING:
- return u"等待执行"
- elif self.status == self.Status.RUNNING:
- return u"运行中"
- elif self.status == self.Status.END:
- return u"运行结束"
- elif self.status == self.Status.TIMEOUT:
- return u"订单超时"
- elif self.status == self.Status.TIMEOUT:
- return u"订单失败"
- elif self.status == self.Status.UNKNOWN:
- return u"状态未知"
- elif self.status == self.Status.WAIT_PAY:
- return u"支付中"
- def to_dealer_list(self):
- data = {
- "id": str(self.id),
- "title": "{}-{}".format(self.logicalCode, self.port),
- "nickname": self.nickname,
- "ownerId": self.ownerId,
- "amount": str(self.price),
- "createdTime": self.dateTimeAdded.strftime("%Y-%m-%d %H:%M:%S"),
- "orderStatus": self.statusText
- }
- return data
- @property
- def refund_info(self):
- refund = self.refund
- rv = {
- "isRefund": refund.isPaid,
- "refundedMoney": refund.totalAmount,
- "fullRefund": refund.totalAmount == self.payment.totalAmount
- }
- return rv
- @property
- def info_for_feedback(self):
- rv = {
- 'orderId': str(self.id),
- 'orderNo': self.orderNo,
- 'coins': self.coin,
- 'deviceStartTime': self.device_start_time,
- 'deviceFinishedTime': self.device_end_time
- }
- rv.update(self.device_identity_info)
- rv.update(self.refund_info)
- rv.update({
- 'port': self.port,
- })
- if self.isNormal:
- rv['reason'] = self.serviceInfo.get('reason', '')
- else:
- rv['reason'] = self.descriptione
- return rv
- class BalanceLog(Searchable):
- meta = {
- 'abstract': True,
- }
- bAmount = MonetaryField(verbose_name=u"变动前充值余额", required=True)
- bBestowAmount = MonetaryField(verbose_name=u"变动前赠送余额", required=True)
- aAmount = MonetaryField(verbose_name=u"变动后充值余额", required=True)
- aBestowAmount = MonetaryField(verbose_name=u"变动后赠送金额", required=True)
- # 充值订单 1 退费订单 2 消费订单 3
- category = IntField(verbose_name=u"变动类型", choices=UserBalanceChangeCategory.choices())
- sourceObj = GenericLazyReferenceField(verbose_name=u'资金变动来源', required=True)
- dateTimeAdded = DateTimeField(verbose_name=u"变动时间", default=datetime.datetime.now)
- @cached_property
- def source(self):
- return self.sourceObj.fetch()
- @property
- def beforeBalance(self):
- return self.bAmount + self.bBestowAmount
- @property
- def afterBalance(self):
- return self.aBestowAmount + self.aAmount
- def to_dict(self):
- data = {
- "id": str(self.id),
- "category": self.category,
- "beforeBalance": self.beforeBalance,
- "afterBalance": self.afterBalance,
- "dateTimeAdded": self.dateTimeAdded.strftime("%Y-%m-%d %H:%M:%S"),
- "sourceId": str(self.sourceObj.id)
- }
- return data
- class UserBalanceLog(BalanceLog):
- """
- 用户账户余额变化记录
- """
- openId = StringField(verbose_name=u"资金主体标识", required=True)
- productAgentId = StringField(verbose_name=u"平台标识", required=True)
- @classmethod
- def consume(cls, user, afterAmount, afterBestowAmount, consumeAmount, consumeBestowAmount, order): # type: (MyUser, RMB, RMB, RMB, RMB, ConsumeRecord) -> UserBalanceLog
- return cls(
- openId=user.openId,
- productAgentId=user.productAgentId,
- bAmount=afterAmount+consumeAmount,
- bBestowAmount=afterBestowAmount+consumeBestowAmount,
- aAmount=afterAmount,
- aBestowAmount=afterBestowAmount,
- sourceObj=order,
- category=UserBalanceChangeCategory.CONSUME
- ).save()
- @classmethod
- def recharge(cls, user, afterAmount, afterBestowAmount, chargeAmount, chargeBestowAmount, order):
- return cls(
- openId=user.openId,
- productAgentId=user.productAgentId,
- bAmount=afterAmount-chargeAmount,
- bBestowAmount=afterBestowAmount-chargeBestowAmount,
- aAmount=afterAmount,
- aBestowAmount=afterBestowAmount,
- sourceObj=order,
- category=UserBalanceChangeCategory.RECHARGE
- ).save()
- @classmethod
- def refund(cls, user, afterAmount, afterBestowAmount, refundAmount, refundBestowAmount, order):
- return cls(
- openId=user.openId,
- productAgentId=user.productAgentId,
- bAmount=afterAmount-refundAmount,
- bBestowAmount=afterBestowAmount-refundBestowAmount,
- aAmount=afterAmount,
- aBestowAmount=afterBestowAmount,
- sourceObj=order,
- category=UserBalanceChangeCategory.REFUND
- ).save()
- @classmethod
- def get_logs(cls, user, pageIndex, pageSize): # type:(MyUser, int, int) -> QuerySet
- return cls.objects.filter(
- openId=user.openId,
- productAgentId=user.productAgentId
- ).skip((pageIndex-1)*pageSize).limit(pageSize)
- class CardBalanceLog(BalanceLog):
- cardId = StringField(verbose_name=u"资金主体标识", required=True)
- openId = StringField(verbose_name=u"卡的持有人", required=True)
- @classmethod
- def consume(cls, card, afterAmount, afterBestowAmount, consumeAmount, consumeBestowAmount, order): # type: (Card, RMB, RMB, RMB, RMB, ConsumeRecord) -> UserBalanceLog
- return cls(
- cardId=str(card.id),
- openId=card.openId,
- bAmount=afterAmount + consumeAmount,
- bBestowAmount=afterBestowAmount + consumeBestowAmount,
- aAmount=afterAmount,
- aBestowAmount=afterBestowAmount,
- sourceObj=order,
- category=UserBalanceChangeCategory.CONSUME
- ).save()
- @classmethod
- def recharge(cls, card, afterAmount, afterBestowAmount, chargeAmount, chargeBestowAmount, order):
- return cls(
- cardId=str(card.id),
- openId=card.openId,
- bAmount=afterAmount - chargeAmount,
- bBestowAmount=afterBestowAmount - chargeBestowAmount,
- aAmount=afterAmount,
- aBestowAmount=afterBestowAmount,
- sourceObj=order,
- category=UserBalanceChangeCategory.RECHARGE
- ).save()
- @classmethod
- def refund(cls, card, afterAmount, afterBestowAmount, refundAmount, refundBestowAmount, order):
- return cls(
- cardId=str(card.id),
- openId=card.openId,
- bAmount=afterAmount - refundAmount,
- bBestowAmount=afterBestowAmount - refundBestowAmount,
- aAmount=afterAmount,
- aBestowAmount=afterBestowAmount,
- sourceObj=order,
- category=UserBalanceChangeCategory.REFUND
- ).save()
- @classmethod
- def get_logs(cls, card, pageIndex, pageSize):
- return cls.objects.filter(
- cardId=str(card.id)
- ).skip((pageIndex - 1) * pageSize).limit(pageSize)
|