1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819 |
- # -*- 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
- 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
- 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.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, AppPlatformType.JD]:
- 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 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)
- @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 attach_split_info(self, partitions):
- agent_partitions = partitions.get(PARTITION_ROLE.AGENT, list())
- if agent_partitions:
- for agentShare in agent_partitions:
- if RMB(agentShare['money']) > RMB(0):
- agent = Agent.objects.get(id = agentShare["id"])
- _email = agent.get_merchant_split_id()
- if not _email:
- logger.error(u"分账代理商未开通商户,agent id = {}".format(agentShare["id"]))
- raise UserServerException(u"分账代理商未开通商户")
- agentShare['merchantId'] = _email
- partner_partitions = partitions.get(PARTITION_ROLE.PARTNER, list())
- if partner_partitions:
- for partnerShare in partner_partitions:
- if RMB(partnerShare['money']) > RMB(0):
- dealer = Dealer.objects.get(id = partnerShare["id"])
- _email = dealer.get_merchant_split_id()
- if not _email:
- logger.error(u"分账代理商未开通商户,dealer id = {}".format(partnerShare["id"]))
- raise UserServerException(u"分账合伙人未开通商户")
- partnerShare['merchantId'] = _email
- owner_partitions = partitions.get(PARTITION_ROLE.OWNER, list())
- assert len(owner_partitions) == 1, u'经销商只能有一个'
- ownerShare = owner_partitions[0]
- if Percent(ownerShare["share"]) < Percent(1):
- raise ValueError(u"dealer jd share should not lt 0.01")
- if RMB(ownerShare['money']) <= RMB(0):
- raise UserServerException(u"经销商商户配置错误")
- ownerShare['merchantId'] = self.owner.get_merchant_split_id()
- return partitions
- 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):
- class PayResult(IterConstant):
- UNPAY = 'unPay'
- SUCCESS = 'success'
- FAILED = 'failed'
- REFUNDING = 'refunding'
- CANCEL = 'cancel'
- 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)
- 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(verbose_name=u"支付完成时间")
- # 派币需要记录信息
- operator = StringField(verbose_name=u"操作员")
- 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'提现商户平台标识,资金池模式下和代理商相同')
- isAllocatedCardMoney = BooleanField(verbose_name=u'是否分钱了', default=False)
- 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 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 is_cancel(self):
- return self.result == self.PayResult.CANCEL
- @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.WECHAT_MINI, PayAppType.ALIPAY]:
- payload['extraInfo'].update({
- 'payOpenId': context.user.get_bound_pay_openid(gateway.bound_openid_key)
- })
- elif gateway.pay_app_type == PayAppType.JD_AGGR and gateway.gateway_type in [AppPlatformType.WECHAT, AppPlatformType.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.WECHAT_MINI, PayAppType.ALIPAY]:
- payload['extraInfo'].update({
- 'payOpenId': user.get_bound_pay_openid(payment_gateway.bound_openid_key)
- })
- elif payment_gateway.pay_app_type == PayAppType.JD_AGGR and \
- payment_gateway.gateway_type in [AppPlatformType.WECHAT, AppPlatformType.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, wxOrderNo, **kwargs):
- # 这里和数据库强相关, 通过判断是否正确更新记录
- payload = {
- 'wxOrderNo': wxOrderNo
- }
- if kwargs:
- payload.update(kwargs)
- payload.update({'result': self.PayResult.SUCCESS})
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id), 'result': {'$ne': self.PayResult.SUCCESS}},
- 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
- def is_success(self):
- return self.result == self.PayResult.SUCCESS
- def is_fail(self):
- return self.result == self.PayResult.FAILED
- 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 ConsumeRecord(OrderRecordBase):
- # """
- # 用户的消费订单记录
- # """
- #
- # if TYPE_CHECKING:
- # state = None
- #
- # def is_waitPay(self): pass
- #
- # def p_to_f(self): pass
- #
- # def c_to_s(self): pass
- #
- # def s_to_e(self): pass
- #
- # def is_end(self): pass
- #
- # def e_to_p(self): pass
- #
- # class Status(IterConstant):
- # CREATED = 'created'
- # WAITING = 'waiting'
- #
- # RUNNING = 'running'
- # END = "end" # 结束运行的状态
- # FINISHED = 'finished'
- # WAIT = 'waitPay'
- # TIMEOUT = 'timeout'
- # FAILURE = 'failure'
- # UNKNOWN = 'unknown'
- #
- # # TODO: 以下三个状态蓝牙使用过, 后续整改
- # INIT = 'init'
- # CHARGED = 'Charged'
- # REFUND = 'Refund'
- #
- # orderNo = StringField(verbose_name = "订单号", default = "")
- # ownerId = StringField(verbose_name = '经销商ID', default = "")
- # price = MonetaryField(verbose_name = "消耗的钱(硬币)", default = RMB('0.00'))
- #
- # #: 在部分场合,设备会失败启动,但这时应该添加该字段为False方便追溯
- # isNormal = BooleanField(verbose_name = "设备是否正常启动", default = True)
- # errorDesc = StringField(verbose_name = u"错误描述", default = '')
- #
- # remarks = StringField(verbose_name = u"备注(默认值不能为None, 否则在使用in的时候出错)", default = '')
- #
- # dateTimeCompletion = DateTimeField(verbose_name = '结单时间', default = None)
- #
- # sequanceNo = StringField(verbose_name = u'消费的流水号,用于和设备上的订单关联', default = None)
- #
- # status = StringField(verbose_name = u'状态', default = '')
- #
- # startKey = StringField(verbose_name = u'启动key')
- #
- # attachParas = DictField(verbose_name = u'用户选择的额外参数', default = {}) # 比如端口、电池类型等信息
- #
- # startTime = DateTimeField(verbose_name = u'设备启动时间', default = None)
- # finishedTime = DateTimeField(verbose_name = u'设备结束时间', default = None)
- #
- # servicedInfo = DictField(verbose_name = u'服务内容', default = {})
- # desc = StringField(verbose_name = u'描述', default = None)
- #
- # feedbackId = ObjectIdField(verbose_name = u'用户反馈ID', default = None)
- #
- # refunded = BooleanField(verbose_name = u'是否已经处理完退费', default = None)
- #
- # aggInfo = DictField(verbose_name = u'聚合信息', default = {})
- #
- # rechargeRcdId = StringField(verbose_name = u'如果是快捷支付,可以关联充值订单(比如昌原,没有金币的概念,需要查询每单的花费)', default = None)
- #
- # # {via: coin/card/virtualCard/free/netPay itemId: 相应的id money: 现金金额 coins: 金币支付金额}
- # payInfo = ListField(verbose_name = u'支付的相关途径', default = [])
- # paymentInfo = DictField(verbose_name = u'支付的相关途径', default = {})
- #
- # package = DictField(verbose_name = u'套餐', default = {})
- #
- # # role: 'main', list<id>; rule: 'sub', id
- # association = DictField(verbose_name = u'关联单', default = {})
- #
- # # 以下字段不在使用
- # leftBalance = StringField(verbose_name = u"心跳机制返回的剩余金额", default = None)
- # rechargeRcdIds = ListField(verbose_name = u'现金充值的订单id', default = None)
- #
- # search_fields = ('openId', 'devNo', 'orderNo', 'remarks', 'address', 'groupName', 'logicalCode')
- #
- # _shard_key = ('ownerId', 'dateTimeAdded')
- #
- # _origin_meta = {
- # "collection": "ConsumeRecord",
- # "db_alias": "default"
- # }
- #
- # meta = _origin_meta
- #
- # # 订单的5种基础状态 后续有需要可以添加
- # states = ["created", "wait", "running", "finished", "waitPay", "end"]
- #
- # # 两种支付流程各个状态的切换
- # # created ---> start ---> end ---> waitPay ---> finished
- # # created ---> waitPay ---> start ---> end ---> finished
- #
- # def __init__(self, *args, **kwargs):
- # # super(ConsumeRecord, self).__init__(*args, **kwargs)
- # Searchable.__init__(self, *args, **kwargs)
- #
- # # 这个目前只有后付费的订单支持这个 做了一个简易的开关
- # # mongoDB 使用save的时候 会有created 以及 update的区别 已知的情况是 在save_created的时候,如果运行了_add_state_machine 会造成数据插入异常
- # if self.id and self.devTypeCode and self.status in ConsumeRecord.states:
- # self._add_state_machine()
- #
- # def __str__(self):
- # return '{}<id={} orderNo={}>'.format(self.__class__.__name__, str(self.id), self.orderNo)
- #
- # @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.ownerId:
- # return None
- # else:
- # dealer = Dealer.objects(id = self.ownerId).first()
- # setattr(self, _attr_name, dealer)
- # return dealer
- #
- # def _add_state_machine(self):
- # """
- # 为订单对象创建状态机 并初始化状态机
- # :return:
- # """
- # self._machine = Machine(model = self, states = ConsumeRecord.states, initial = self.status,
- # after_state_change = ["_after_state_change"], queued = True, auto_transitions = False)
- #
- # # 订单创建之后 需要将订单转换到下一个状态,订单的转换状态是根据订单是否正常来切换的
- # self._machine.add_transition("c_to_s", "created", "running", conditions = ['isNormal'])
- # self._machine.add_transition("c_to_s", "created", "finished", unless = ["isNormal"])
- #
- # # 订单运行状态到订单停止运行状态
- # self._machine.add_transition("s_to_e", "running", "end", after = ["_pay_coins", "e_to_f"])
- # # 订单虚拟货币支付, 支付成功之后将订单状态切换为结束,结束之后需要有数据汇总的操作 支付不成功智能等待用户主动现金支付
- # self._machine.add_transition("e_to_f", "end", "finished", conditions = ["_isPaid"],
- # after = ["_stats_info", "_do_dev_callback"])
- # self._machine.add_transition("e_to_e", "end", "end", unless = ["_isPaid"])
- # self._machine.add_transition("e_to_p", "end", "waitPay", after = ["_add_pay_time_point"])
- #
- # # 订单支后,轮询的回调函数,一旦轮询到订单支付成功, 则将状态改变 由于不知道end状态是会首先变成paying再变成finished 还是直接由end变为finished, 接受的初始状态实体应该是一个列表
- # self._machine.add_transition("p_to_f", "waitPay", "finished", conditions = ["_isRecharge", "_isNotPaid"],
- # after = ["_pay_cash", "_do_dev_callback"])
- # self._machine.add_transition("p_to_f", "waitPay", "end", unless = ["_isRecharge", "_isPaid"],
- # after = ["_clear_recharge"])
- #
- # def _after_state_change(self):
- # """
- # 订单使用状态机切换状态之后,将订单的status相应修改,并且重载一下
- # :return:
- # """
- # self.update(status = self.state)
- # self.reload()
- #
- # def _pay_coins(self):
- # """
- # 订单运行结束之后,直接尝试使用虚拟货币对订单进行支付
- # :return:
- # """
- #
- # # 已经支付了或者已经有充值订单相关联
- # if self._isPaid or self._isRecharge:
- # return
- #
- # # 尝试使用包月卡支付
- # dev = Device.get_dev(self.devNo)
- # package = dev.get("washConfig", dict()).get(self.attachParas.get("packageId", "1"))
- #
- # monthlyPackage = MonthlyPackage.get_enable_one(self.openId, self.groupId)
- # if monthlyPackage and monthlyPackage.is_suit_package(package):
- # monthlyPackage.deduct(self)
- # self.paymentInfo = { # zjl [] -> {}
- # "via": "monthlyPackage",
- # "itemId": str(monthlyPackage.id),
- # "money": RMB(0).mongo_amount,
- # "coins": VirtualCoin(0).mongo_amount
- # } # zjl [] -> {}
- # self.save()
- # return
- #
- # dev = Device.get_dev(self.devNo) # type: DeviceDict
- # group = Group.get_group(self.groupId)
- # dealer = dev.owner # type: Dealer
- #
- # devTypeCode = dev.get("devType", dict()).get("code", "")
- #
- # # 尝试使用虚拟卡支付
- # # package = {"unit": u"币", "count": self.coin}
- # vCards = UserVirtualCard.get_user_cards(self.openId, self.groupId, self.ownerId)
- # for vCard in vCards:
- # if vCard.support_dev_type(devTypeCode) and vCard.can_use_today(package):
- # # TODO zjl 先简陋处理一下 后续将其更换位置
- # from apps.web.user.utils import vCard_pay_coin
- # vCard_pay_coin(self, vCard, dev, group, package, self.attachParas) # type: VCardConsumeRecord
- # return
- #
- # user = MyUser.objects(openId = self.openId, groupId = self.groupId).first() # type: MyUser
- # balance = user.calc_currency_balance(dealer, group)
- #
- # if balance >= VirtualCoin(self.coin):
- # # 金币足够支付的情况下 直接使用金币支付
- # from apps.web.user.utils import user_pay_coin
- # user_pay_coin(self.openId, self.devNo, self.groupId, self.ownerId, self.coin)
- # self.paymentInfo = {
- # "via": "coin",
- # "itemId": "",
- # "money": RMB(0).mongo_amount,
- # "coins": VirtualCoin(self.coin).mongo_amount
- # }
- # self.save()
- #
- # def _clear_recharge(self):
- # self.rechargeRcdId = ""
- # self.save()
- #
- # def _pay_cash(self):
- # # 这一步的时候实际上已经支付成功了,所需要做的步骤就是将支付的信息加载进 payInfo 字段中
- # self.paymentInfo = {
- # "via": "cash",
- # "itemId": self.rechargeRcdId,
- # "money": RMB(self.money).mongo_amount,
- # "coins": VirtualCoin(0).mongo_amount
- # }
- # self.save()
- #
- # @property
- # def _isPaid(self):
- # """
- # 检查用户是否已经为此订单付了钱 无论是此订单关联的recharge 还是使用金币支付 或者是使用虚拟卡支付
- # :return:
- # """
- # return any([self.payInfo, self.paymentInfo])
- #
- # @property
- # def _isNotPaid(self):
- # return not self._isPaid
- #
- # @property
- # def _isRecharge(self):
- # """
- # 检测订单是否已经与充值订单相关联
- # :return:
- # """
- # rechargeRcdId = self.recharge_record_id
- #
- # if not rechargeRcdId:
- # return False
- #
- # try:
- # from apps.web.common.proxy import ClientRechargeModelProxy
- # record = ClientRechargeModelProxy.get_one(
- # shard_filter = {'ownerId': self.ownerId},
- # id = str(rechargeRcdId)) # type: RechargeRecord
- # except DoesNotExist:
- # return False
- # except Exception as e:
- # logger.exception(e)
- # return False
- #
- # if record.result == RechargeRecord.PayResult.SUCCESS:
- # # 下面还要用的,防止重复查询
- # setattr(self, "_rechargeRcd", record)
- # return True
- # else:
- # return False
- #
- # def _do_dev_callback(self):
- # dev = Device.get_dev(self.devNo)
- # box = ActionDeviceBuilder.create_action_device(dev)
- # if box.isHaveCallback():
- # try:
- # box.do_callback(self)
- # except Exception:
- # self.attachParas.update({"callbackResult": False})
- # self.save()
- # self.attachParas.update({"callbackResult": True})
- # self.save()
- #
- # def _add_pay_time_point(self):
- #
- # self.attachParas['payTimePoint'] = datetime.datetime.now()
- # self.save()
- #
- # @property
- # def is_from_api(self):
- # return False
- #
- # @property
- # def is_on_point(self):
- # return False
- #
- # @property
- # def is_from_user(self):
- # return True
- #
- # @property
- # def used_port(self):
- # port = self.attachParas['chargeIndex'] if (
- # self.attachParas and self.attachParas.has_key('chargeIndex')
- # and self.attachParas['chargeIndex']) else -1
- # try:
- # return int(port)
- # except:
- # return port
- #
- # @property
- # def created_date(self):
- # return self.dateTimeAdded
- #
- # @property
- # def completion_date(self):
- # if self.dateTimeCompletion:
- # return self.dateTimeCompletion
- # else:
- # return self.finishedTime
- #
- # @property
- # def device_start_time(self):
- # if self.startTime:
- # return self.startTime
- # else:
- # return self.dateTimeAdded
- #
- # @property
- # def device_finished_time(self):
- # if self.finishedTime:
- # return self.finishedTime
- # else:
- # return ''
- #
- # @classmethod
- # def new_one(cls, order_no, user, device, group, package, attach_paras, pay_info = None,
- # start_key = None, sequence_no = None):
- # # type: (str, MyUser, DeviceDict, GroupDict, Dict, Dict, Dict, str, str)->ConsumeRecord
- #
- # order = cls(
- # orderNo = order_no,
- # time = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
- # openId = user.openId,
- # nickname = user.nickname,
- # coin = VirtualCoin(0),
- # money = RMB(0),
- # devNo = device.devNo,
- # logicalCode = device.logicalCode,
- # devTypeCode = device.devTypeCode,
- # devTypeName = device.devTypeName,
- # groupId = device.groupId,
- # ownerId = device.ownerId,
- # address = group.address,
- # groupNumber = device.groupNumber,
- # groupName = group.groupName,
- # startKey = start_key,
- # isNormal = False,
- # errorDesc = '',
- # sequanceNo = sequence_no,
- # attachParas = attach_paras,
- # status = cls.Status.CREATED,
- # paymentInfo = pay_info,
- # package = package)
- #
- # remarks = None
- # money = RMB(0)
- # coins = VirtualCoin(0)
- #
- # if pay_info:
- # if pay_info['via'] == 'free':
- # remarks = u'免费'
- # elif pay_info['via'] == 'virtualCard':
- # remarks = u'虚拟卡券'
- # elif pay_info['via'] == 'coins':
- # coins = pay_info['coins']
- # remarks = u'余额支付'
- # elif pay_info['via'] == 'card':
- # coins = pay_info['coins']
- # money = pay_info['coins']
- # remarks = u'刷卡消费'
- # else:
- # money = pay_info['money']
- # coins = pay_info['coins']
- # remarks = u'网络支付'
- #
- # order.paymentInfo = pay_info
- # order.remarks = remarks
- # order.money = money
- # order.coin = coins
- #
- # order.save()
- #
- # return order
- #
- # def update_service_info(self, consumeDict, isFinished=True):
- # # type:(dict, bool)->None
- #
- # try:
- # trans_consume_dict = {}
- #
- # for key, value in consumeDict.iteritems():
- # if value is None:
- # logger.debug('{} is none.'.format(key))
- # continue
- #
- # if hasattr(value, 'mongo_amount'):
- # trans_consume_dict[key] = value.mongo_amount
- # else:
- # trans_consume_dict[key] = value
- #
- # self.servicedInfo.update(trans_consume_dict)
- # self.save()
- #
- # if not isFinished:
- # return
- #
- # status = self.update_agg_info(trans_consume_dict)
- # if status:
- # record_consumption_stats(self)
- # else:
- # logger.error(
- # '[update_progress_and_consume_rcd] failed to update_agg_info record=%r' % (self,))
- # except Exception, e:
- # logger.exception('update agg info error=%s' % e)
- #
- # def update_for_end(self, serviceInfo = None, finishedTime=datetime.datetime.now()):
- # """
- # 订单切换到设备运行结束的状态
- # :param serviceInfo:
- # :param finishedTime
- # :return:
- # """
- #
- # self.status = self.Status.FINISHED
- # self.finishedTime = to_datetime(finishedTime)
- #
- # serviceInfo = serviceInfo or dict()
- #
- # self.update_service_info(consumeDict=serviceInfo)
- #
- # @property
- # def receiptDesc(self):
- # rv = {
- # 'orderNo': self.orderNo,
- # 'createdTime': self.dateTimeAdded,
- # 'payment': u'{} 金币'.format(str(VirtualCoin(self.coin)))
- # }
- #
- # if self.paymentInfo:
- # if self.paymentInfo['via'] == 'free':
- # rv.update({'payment': u'本次免费'})
- # return rv
- # elif self.paymentInfo['via'] == 'cash':
- # rv.update({'payment': u'{} 元'.format(self.paymentInfo['money'])})
- # return rv
- # elif self.paymentInfo['via'] == 'virtualCard':
- # rv.update({'payment': u'虚拟卡抵扣'})
- # return rv
- # elif self.paymentInfo['via'] == 'yuchuanCard':
- # rv.update({'payment': u'{} 元 宇川一卡通'.format(self.paymentInfo['money'])})
- # return rv
- #
- # if self.remarks == u'虚拟卡消费':
- # rv.update({'payment': u'虚拟卡抵扣'})
- # return rv
- #
- # if self.remarks == u'包月卡消费':
- # rv.update({'payment': u'包月卡抵扣'})
- # return rv
- #
- # if self.remarks == 'YuChuanCard':
- # rv.update({'payment': u'{} 元 宇川一卡通'.format(self.paymentInfo['money'])})
- # return rv
- #
- # return rv
- #
- # def _stats_info(self):
- # """
- # 订单切换为最终完成状态之后 将订单的信息上报到数据中心进行统计
- # :return:
- # """
- # duration = self._duration
- # if duration:
- # agg_info = {
- # DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
- # DEALER_CONSUMPTION_AGG_KIND.COIN: self.coin
- # }
- # else:
- # agg_info = {
- # DEALER_CONSUMPTION_AGG_KIND.COIN: self.coin
- # }
- #
- # status = self.update_agg_info(agg_info)
- # if status:
- # record_consumption_stats(self)
- #
- # @property
- # def _duration(self):
- # """
- # 计算订单的持续时长
- # :return:
- # """
- # if self.state != "finished" or not self.finishedTime:
- # return 0
- #
- # if 'duration' in self.servicedInfo and isinstance(self.servicedInfo['duration'], (float, int)):
- # duration = self.servicedInfo['duration']
- #
- # else:
- # # 计算时长不需要很精确,直接使用round算了
- # duration = round((self.finishedTime - self.dateTimeAdded).total_seconds() / 60.0)
- #
- # return duration
- #
- # @property
- # def virtual_card_id(self):
- # if not self.paymentInfo:
- # return None
- #
- # via = self.paymentInfo.get('via', None)
- #
- # if via == 'virtualCard':
- # return self.paymentInfo['itemId']
- # else:
- # return None
- #
- # @property
- # def recharge_record_id(self):
- # if self.rechargeRcdId and str(self.rechargeRcdId) != 'None':
- # return str(self.rechargeRcdId)
- #
- # if not self.paymentInfo:
- # return None
- #
- # via = self.paymentInfo.get('via', None)
- # if via in ['netPay', 'cash', 'coins']:
- # if 'itemId' in self.paymentInfo and self.paymentInfo['itemId']:
- # return str(self.paymentInfo['itemId'])
- # else:
- # return None
- # else:
- # return None
- #
- # @property
- # def payTypeInfo(self):
- # cardId = self.attachParas.get("cardId")
- # vCardId = self.attachParas.get("vCardId")
- # data = dict()
- #
- # if cardId:
- # try:
- # cardNo = Card.objects.get(id = cardId).cardNo
- # except Exception as e:
- # logger.exception(e)
- # else:
- # data.update({"cardId": cardId, "cardNo": cardNo})
- #
- # if vCardId:
- # try:
- # vCardNo = UserVirtualCard.objects.get(id = vCardId).cardNo
- # except Exception as e:
- # logger.exception(e)
- # else:
- # data.update({"vCardId": vCardId, "vCardNo": vCardNo})
- #
- # return data
- #
- # @staticmethod
- # def make_no(*args, **kwargs):
- # timestamp = generate_timestamp_ex()
- # random_int = random.randint(1, 1000)
- # return "%d%03d" % (timestamp, random_int)
- #
- # @staticmethod
- # def make_order_no(identifier, sub_type):
- # return OrderNoMaker.make_order_no_32(identifier, OrderMainType.CONSUME, sub_type)
- #
- # @property
- # def consume_type(self):
- # if self.remarks:
- # if u'刷卡' in self.remarks:
- # return 'card'
- # elif u'虚拟卡' in self.remarks:
- # return 'mobile_vcard'
- # elif u'投币或者刷卡消费' in self.remarks:
- # return 'offline'
- # elif u'包月卡' in self.remarks:
- # return 'monthlyPackage'
- #
- # if self.recharge_record_id:
- # return 'cash'
- # else:
- # return 'balance'
- #
- # def to_user_detail(self):
- # """
- # 消费记录对于用户端显示的详细信息
- # :return:
- # """
- #
- # data = {
- # 'id': str(self.id),
- #
- # 'consumeType': self.consume_type,
- #
- # 'orderNo': self.orderNo,
- #
- # 'ownerId': self.ownerId,
- #
- # 'money': self.money,
- #
- # 'coins': self.coin,
- #
- # 'createdTime': self.created_date,
- # 'completionTime': self.completion_date,
- #
- # 'startResult': 'success' if self.isNormal else 'failed',
- # 'errorDesc': self.errorDesc,
- #
- # 'deviceStatTime': self.device_start_time,
- # 'deviceFinishedTime': self.device_finished_time,
- #
- # 'reason': self.servicedInfo.get("reason", "")
- # }
- #
- # data.update(self.device_identity_info)
- #
- # data.update(self.refund_info)
- #
- # if self.recharge_record_id:
- # recharge_record = RechargeRecord.objects(id = self.recharge_record_id).first() # type: RechargeRecord
- # if recharge_record:
- # data.update({
- # 'rechargeRcdId': str(recharge_record.id),
- # 'rechargeTradeNo': recharge_record.orderNo,
- # })
- #
- # # 兼容之前的订单 凡是没有订单状态机的订单,一律视为 之前的订单
- # # 简陋 的实现以下订单状态改变
- # if self.devTypeCode in [
- # Const.DEVICE_TYPE_CODE_CABINET,
- # Const.DEVICE_TYPE_CODE_CHARGE_XIAOKEDOU,
- # Const.DEVICE_TYPE_CODE_CABINET_NEW,
- # Const.DEVICE_TYPE_CODE_CHARGE_WEIFULE_CAR,
- # Const.DEVICE_TYPE_CODE_CAR_WEIFULE_CHARGING_DOUB,
- # Const.DEVICE_TYPE_CODE_CAR_WEIFULE_21KW,
- # Const.DEVICE_TYPE_CODE_CAR_WEIFILE_HOME_JFPG,
- # Const.DEVICE_TYPE_CODE_CAR_WEIFILE_HOME_DOUB_JFPG,
- # ] + support_policy_device + support_policy_weifule:
- # self.charge_recharge_status()
- # try:
- # data.update({"orderStatus": self.state})
- # except Exception as e:
- # logger.exception(e)
- #
- # if self.servicedInfo:
- # self.servicedInfo.pop('refundedMoney', None)
- # self.servicedInfo.pop('refundedCash', None)
- # data.update(self.servicedInfo)
- #
- # data.update(self.refund_info)
- #
- # return data
- #
- # @property
- # def summary(self):
- # return {
- # 'id': str(self.id),
- #
- # 'consumeType': self.consume_type,
- #
- # 'ownerId': self.ownerId,
- #
- # 'logicalCode': self.logicalCode,
- #
- # 'groupName': self.groupName,
- # 'devTypeName': self.dev_type_name,
- #
- # 'money': self.money,
- # 'coins': self.coin,
- #
- # 'createdTime': self.dateTimeAdded.strftime(Const.DATETIME_FMT),
- # }
- #
- # def to_detail(self, source = None, hides = []):
- # # type: (Optional[str], Optional[list])->dict
- #
- # data = {
- # 'id': str(self.id),
- #
- # 'orderNo': self.orderNo,
- #
- # 'createdTime': self.created_date,
- # 'completionTime': self.completion_date,
- #
- # 'deviceStatTime': self.device_start_time,
- # 'deviceFinishedTime': self.device_finished_time,
- #
- # 'orderAmount': self.coin,
- #
- # 'amount': self.coin,
- # 'coins': self.coin,
- #
- # '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.dev_type_name, # 兼容
- # 'devTypeName': self.dev_type_name,
- #
- # 'remarks': self.remarks,
- #
- # 'startResult': 'success' if self.isNormal else 'failed',
- #
- # 'errorDesc': self.errorDesc,
- #
- # 'servicedInfo': [u'%s: %s'.encode('utf-8')
- # % (GLOSSARY_TRANSLATION.get(k, k), v)
- # for k, v in self.servicedInfo.iteritems()
- # if (k not in self.aggInfo and k in GLOSSARY_TRANSLATION)],
- #
- # 'aggInfo': self.consumption_stats(source = source, hides = hides),
- #
- # # 以下字段和以前做兼容
- # 'finishedTime': self.device_finished_time,
- # 'port': self.used_port
- # }
- #
- # if self.rechargeRcdId:
- # recharge_record = RechargeRecord.objects(id = self.rechargeRcdId).first() # type: RechargeRecord
- # if recharge_record:
- # data.update({
- # 'gateway': recharge_record.gateway,
- # 'rechargeRcdId': str(recharge_record.id),
- # 'rechargeTradeNo': recharge_record.orderNo,
- # 'gatewayTradeNo': recharge_record.wxOrderNo # 兼容
- # })
- #
- # if self.redpackId:
- # redPack = Redpack.get_one(self.redpackId)
- # if redPack:
- # package = self.package
- # coins = package.get('coins')
- # price = package.get('price')
- # redpackMoney = redPack['money']
- #
- # if RMB(redpackMoney) >= RMB(price): # 消费中红包抵扣 做一个保护
- # redpackAmount = coins
- # else:
- # redpackAmount = round(float(RMB(redpackMoney)) / price * coins, 2)
- #
- # data.update({'redpackAmount': redpackAmount})
- #
- # if self.devTypeCode in ["1008031", "100803"] and self.status:
- # data.update({"status": self.status})
- #
- # payTypeInfo = self.payTypeInfo
- # if payTypeInfo:
- # data.update(payTypeInfo)
- #
- # return data
- #
- # def charge_recharge_status(self):
- # if not hasattr(self, "is_waitPay") or not self.is_waitPay():
- # return
- #
- # if self.recharge_record_id:
- # from apps.web.common.proxy import ClientRechargeModelProxy
- #
- # rechargeRecord = ClientRechargeModelProxy.get_one(
- # shard_filter = {'ownerId': self.ownerId},
- # id = self.recharge_record_id) # type: RechargeRecord
- # if int((datetime.datetime.now() - rechargeRecord.dateTimeAdded).total_seconds()) > 60 * 3:
- # self.p_to_f()
- #
- # def consumption_stats(self, source = None, hides = []):
- # # type:(Optional[str], Optional[list])->list
- # return translate_consumption_stats(self.aggInfo.iteritems(), source, hides)
- #
- # def to_dict(self, source = None, hides = []):
- # # type: (Optional[str], Optional[list])->Dict[str, Union[AnyStr, float]]
- #
- # title = u'设备{logicalCode}消费 {agg_info} '.format(logicalCode = self.logicalCode,
- # agg_info = '-'.join(self.consumption_stats(source, hides)))
- # return {
- # 'id': str(self.id),
- # 'title': title,
- # 'amount': self.coin,
- # 'createdTime': self.created_date,
- # 'source': source,
- # 'ownerId': self.ownerId,
- # 'startResult': 'success' if self.isNormal else 'failed',
- #
- # 'logicalCode': self.logicalCode,
- # 'devTypeName': self.dev_type_name,
- # 'userNickname': self.nickname
- # }
- #
- # def update_agg_info(self, mapping):
- # # type: (dict)->bool
- # """
- #
- # :param mapping:
- # :return:
- # """
- # try:
- # trans_consume_dict = {}
- # for kind, value in mapping.iteritems():
- # if kind not in DEALER_CONSUMPTION_AGG_KIND.choices():
- # logger.debug('{} is not consume stat kind.'.format(kind))
- # continue
- #
- # if value is None:
- # logger.debug('{} is none.'.format(kind))
- # continue
- #
- # if hasattr(value, 'mongo_amount'):
- # trans_consume_dict[kind] = value.mongo_amount
- # else:
- # trans_consume_dict[kind] = value
- #
- # self.aggInfo.update(trans_consume_dict)
- # self.save()
- #
- # except Exception as e:
- # logger.exception(e)
- # return False
- #
- # return True
- #
- # @classmethod
- # def get_consumed_records(cls, **kwargs):
- # return cls.objects(isNormal = True, **kwargs).order_by('-dateTimeAdded')
- #
- # @property
- # def is_temp_package(self):
- # if 'isTempPackage' in self.attachParas:
- # return self.attachParas['isTempPackage']
- #
- # if 'isTempPackage' in self.package:
- # return self.package['isTempPackage']
- #
- # return False
- #
- # @property
- # def is_free(self):
- # if 'isFree' in self.attachParas:
- # return self.attachParas['isFree']
- #
- # if 'isFree' in self.package:
- # return self.package['isFree']
- #
- # return False
- #
- # @property
- # def my_package(self):
- # return PackageDict(self.package)
- #
- # @property
- # def statistic_type(self):
- # return "consumption"
- #
- # @property
- # def dealerIds(self):
- # return [ObjectId(self.ownerId)] + [ObjectId(_['id']) for _ in
- # Group.get_group(self.groupId)['partnerDict'].values()]
- #
- # def get_statistic_update_info(self, amount = None):
- # """
- # 获取 更新的数据
- # :param amount: 设置的金币的金额
- # :return:
- # """
- # kindMap = self.aggInfo or dict()
- #
- # kindMap.setdefault("coin", VirtualCoin(amount or self.coin))
- # hour = self.dateTimeAdded.hour
- #
- # updateOrInsertData = {
- # "add_to_set__origin__{}".format(self.statistic_type): self.id,
- # "inc__daily__totalConsumptionCount": 1
- # }
- #
- # for _kind, _value in kindMap.items():
- # updateOrInsertData["inc__daily__{}__{}".format(self.statistic_type, _kind)] = \
- # to_quantity(_kind, _value).mongo_amount
- # updateOrInsertData["inc__hourly__{}__{}__{}".format(hour, self.statistic_type, _kind)] = \
- # to_quantity(_kind, _value).mongo_amount
- #
- # return updateOrInsertData
- #
- # def deail_link(self, device):
- # # type:(DeviceDict)->str
- #
- # if device.channelType == DeviceChannelType.Channel_BT:
- # if device.deviceAdapter.support_count_down():
- # return concat_bt_count_down_page_url(l = self.logicalCode, port = self.used_port)
- # else:
- # if device.devTypeCode in ['1002121', '1002122']:
- # return concat_front_end_url(uri = '/user/index.html#/user/deviceStatus')
- # else:
- # if device.deviceAdapter.support_count_down(self.openId, self.used_port):
- # return concat_count_down_page_url(devNo = self.devNo, port = self.used_port)
- # else:
- # return concat_front_end_url(uri = '/user/index.html#/user/consumeDetail?id={}'.format(str(self.id)))
- #
- # def update_paid(self, **kwargs):
- # # type: (dict)->None
- #
- # if not self.paymentInfo:
- # logger.debug('consume record<id={}> is old version.'.format(str(self.id)))
- # return
- #
- # self.paymentInfo['payTime'] = datetime.datetime.now()
- #
- # vCardConsumeRecord = kwargs.get('vCardConsumeRecord', None)
- # if vCardConsumeRecord:
- # assert self.paymentInfo['via'] == 'virtualCard', 'is not virtual card pay type.'
- #
- # self.paymentInfo['vCardConsumeRcdId'] = str(vCardConsumeRecord.id)
- # self.paymentInfo['vCardConsumeData'] = {
- # 'consumeData': vCardConsumeRecord.consumeData,
- # 'consumeDayData': vCardConsumeRecord.consumeDayData
- # }
- #
- # self.save()
- #
- # def update_failure(self, errorDesc):
- # self.isNormal = False
- # self.errorDesc = errorDesc
- # if not self.dateTimeCompletion:
- # self.dateTimeCompletion = datetime.datetime.now()
- #
- # self.save()
- #
- # @property
- # def user(self):
- # return MyUser.objects.filter(openId = self.openId, groupId = self.groupId).first()
- #
- # @property
- # def redpackId(self):
- # return self.attachParas.get('redpackId', "")
- #
- # @property
- # def insId(self):
- # return self.attachParas.get("insId", "")
- #
- # @property
- # def monthlyPackageId(self):
- # return self.attachParas.get('monthlyPackageId', "")
- #
- # @property
- # def refund_info(self):
- # rv = {
- # 'isRefund': False
- # }
- #
- # if self.isNormal:
- # if self.servicedInfo.get('refundedMoney') and VirtualCoin(
- # self.servicedInfo.get('refundedMoney')) > VirtualCoin('0.00'):
- # rv['isRefund'] = True
- # rv['refundedMoney'] = str(self.servicedInfo.get('refundedMoney'))
- # elif self.servicedInfo.get('refundedCash') and RMB(self.servicedInfo.get('refundedCash')) > RMB('0.00'):
- # rv['isRefund'] = True
- # rv['refundedCash'] = str(self.servicedInfo.get('refundedCash'))
- # else:
- # if self.status == ConsumeRecord.Status.REFUND: # 蓝牙会设置退费状态
- # if VirtualCoin(self.coin) > VirtualCoin('0.00'):
- # rv['isRefund'] = True
- # rv['refundedMoney'] = str(self.coin)
- # elif self.recharge_record_id:
- # refund_order = RefundMoneyRecord.objects(rechargeObjId = str(self.recharge_record_id)).first()
- # if refund_order:
- # rv['isRefund'] = True
- # rv['refundedCash'] = str(refund_order.money)
- # else:
- # if VirtualCoin(self.coin) > VirtualCoin('0.00'): # 失败的情况下根本不会扣费
- # rv['isRefund'] = True
- # rv['refundedMoney'] = str(self.coin)
- #
- # return rv
- #
- # @property
- # def info_for_feedback(self):
- # rv = {
- # 'orderId': str(self.id),
- # 'orderNo': self.orderNo,
- # 'coins': self.coin,
- # 'deviceStartTime': self.device_start_time,
- # 'deviceFinishedTime': self.device_finished_time
- # }
- #
- # rv.update(self.device_identity_info)
- #
- # rv.update(self.refund_info)
- #
- # if self.used_port != -1:
- # rv.update({
- # 'port': self.used_port
- # })
- #
- # if self.recharge_record_id:
- # from apps.web.common.proxy import ClientRechargeModelProxy
- # recharge_record = ClientRechargeModelProxy.get_one(
- # shard_filter = {'ownerId': self.ownerId}, id = self.recharge_record_id) # type: RechargeRecord
- #
- # if recharge_record:
- # rv.update({
- # 'rechargeTradeNo': recharge_record.orderNo,
- # 'money': self.money
- # })
- #
- # return rv
- #
- 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 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, payment, 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
- @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 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(Searchable):
- """
- 用户退款记录
- """
- class Status(object):
- """ 退款单的状态 正常流程顺序是从上至下 """
- CREATED = 'created' # 创建
- PROCESSING = 'processing' # 申请中
- FAILURE = 'failure' # 申请失败 (彻底失败 需要重新发起)
- SUCCESS = 'success' # 退款成功 (异步结果)
- CLOSED = 'closed' # 退款失败 (异步结果 不需要重新发起)
- # 订单常见的状态
- # created ---> processing ---> success (申请 然后接受成功)
- # created ---> failure (直接申请失败)
- # created ---> processing ---> closed (申请成功 回调失败)
- # created ---> processing ---> failure (申请成功 回调失败 不可以重试)
- rechargeObjId = ObjectIdField(verbose_name = u'对应充值单')
- # refundSeq = IntField(verbose_name=u'多次退款,计算退款序列号', default=1)
- orderNo = StringField(verbose_name = u'商户退款订单号', default = '')
- errorCode = StringField(verbose_name = u"错误代码", default = "")
- errorDesc = StringField(verbose_name = u"错误描述", default = "")
- money = MonetaryField(verbose_name = u"退款的金钱数额", default = RMB('0.00'))
- coins = MonetaryField(verbose_name = u"清理用户金币数", default = VirtualCoin('0.00'))
- status = StringField(verbose_name = u'订单状态', default = Status.CREATED)
- datetimeAdded = DateTimeField(verbose_name = u"退款创建时间", default = datetime.datetime.now)
- datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
- finishedTime = DateTimeField(verbose_name = u"退款到账时间")
- tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = "")
- meta = {
- "collection": "RefundMoneyRecord",
- "db_alias": "default",
- }
- @classmethod
- def issue(cls, order, refundCash, deductCoins):
- # type:(RechargeRecord, RMB, VirtualCoin)->RefundMoneyRecord
- refund_order_no = OrderNoMaker.make_order_no_32(
- identifier = order.logicalCode,
- main_type = OrderMainType.REFUND,
- sub_type = RefundSubType.REFUND)
- return cls(
- rechargeObjId = order.id,
- # refundSeq=next_seq,
- orderNo = refund_order_no,
- money = refundCash,
- coins = deductCoins,
- status = cls.Status.PROCESSING).save()
- def succeed(self, tradeRefundNo, finishedTime): # type:(str, datetime.datetime) -> bool
- """ 更新为成功状态 已经成功退款 """
- result = self.__class__.get_collection().update_one(
- {
- 'orderNo': self.orderNo,
- 'status': self.Status.PROCESSING
- },
- {
- '$set': {
- 'status': self.Status.SUCCESS,
- 'datetimeUpdated': datetime.datetime.now(),
- 'tradeRefundNo': tradeRefundNo,
- 'finishedTime': finishedTime
- }
- }
- )
- return result.matched_count == 1
- def fail(self, errorCode="", errorDesc=""): # type:(str, unicode) -> bool
- """
- 更新为失败状态 表示退款申请失败 这种情况下 可能就需要售后进行介入
- 1. 订单申请发起时候即失败
- 2. 订单申请通过 但是回调的时候明确失败 并且是不可重试的失败
- 3. 此状态即表示订单没退款 理论上可以重新发起退款
- """
- result = self.__class__.objects.filter(
- orderNo=self.orderNo,
- status__in=[self.Status.PROCESSING, self.Status.CREATED]
- ).update(
- status=self.Status.FAILURE,
- datetimeUpdated=datetime.datetime.now(),
- errorCode=errorCode,
- errorDesc=errorDesc
- )
- # TODO 这种情况需不需要将对于经销商的分账重新执行
- return result
- def closed(self, tradeRefundNo, errorCode="", errorDesc=""): # type:(str, str, str) -> bool
- """
- 退款申请成功了 表示合法的退款申请 但是由于某些原因业务进行不下去
- """
- result = self.__class__.objects.filter(
- orderNo=self.orderNo,
- status=self.Status.PROCESSING
- ).update(
- status=self.Status.CLOSED,
- datetimeUpdated=datetime.datetime.now(),
- tradeRefundNo=tradeRefundNo,
- errorCode=errorCode,
- errorDesc=errorDesc
- )
- return result
- def processing(self): # type:() -> bool
- result = self.__class__.objects.filter(
- orderNo=self.orderNo,
- status__in=[self.Status.CLOSED, self.Status.CREATED, self.Status.FAILURE]
- ).update(
- status=self.Status.PROCESSING,
- datetimeUpdated=datetime.datetime.now()
- )
- return result
- @property
- def is_fail(self):
- """ 是否订单失败 """
- return self.status == self.Status.FAILURE
- @property
- def is_closed(self):
- """ 退款单是否已经关闭 目前这个状态没有用 适用于用户手动取消退款申请 """
- return self.status == self.Status.CLOSED
- @property
- def is_success(self):
- """ 退款已经成功 """
- return self.status == self.Status.SUCCESS
- @property
- def is_created(self):
- return self.status == self.Status.CREATED
- @property
- def is_apply(self):
- """ 是否已经发出退款申请 """
- return self.status in [self.Status.CREATED, self.Status.PROCESSING]
- @property
- def is_processing(self):
- return self.status == self.Status.PROCESSING
- @property
- def is_successful(self):
- """ 保留旧的方法 """
- return self.status in [self.Status.SUCCESS, self.Status.PROCESSING]
- @property
- def pay_sub_order(self):
- # type: ()->RechargeRecord
- if not hasattr(self, '__pay_sub_order__'):
- pay_order = RechargeRecord.objects(id = str(self.rechargeObjId)).first()
- 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 refund_order_record(self):
- if not hasattr(self, '__refund_order_record__'):
- order = RechargeRecord.objects(orderNo = self.orderNo).first()
- setattr(self, '__refund_order_record__', order)
- return getattr(self, '__refund_order_record__')
- @refund_order_record.setter
- def refund_order_record(self, refund_order_record):
- setattr(self, '__refund_order_record__', refund_order_record)
- @classmethod
- def get_record(cls, order_no):
- return cls.objects(orderNo = order_no).first()
- @property
- def user(self):
- # type: ()->MyUser
- return self.pay_sub_order.user
- 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)
|