123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783 |
- # -*- 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 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)
- 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
- }
- 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,
- 'completionTime': self.finishedTime,
- 'deviceStatTime': self.device_start_time,
- 'deviceFinishedTime': self.device_end_time,
- 'amount': self.price,
- '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
- 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)
|