+
    Õ…¿jµ*  ã                   ó"  € R t ^ RIt^ RIt^ RIt^ RIt]P
                  ! R4      t^ RIHt ^ RI	H	t	H
t
 ^ RIHt ^ RIHt ^ RIHtHtHtHt ^ RIHt ^ R	IHtHt R
 R ltR R ltR R ltR R ltR R ltR R ltR R ltR R lt R R lt!R# )a¬  Periodic live-status reconciliation for Billing, customers and hotspot vouchers.

Voucher lifecycle rules (independent triggers, either one is enough):
  1. Duration/jatah habis  -> hapus dari RouterOS + database.
  2. Masa berlaku habis   -> hapus dari RouterOS + database.
Status juga disinkronkan LIVE dari RouterOS (uptime / limit-uptime / active /
disabled) supaya DataTables voucher selalu mencerminkan kondisi perangkat.
Nzbestweb.voucher_monitor)Úselect)ÚdatetimeÚtimezone©ÚAsyncSessionLocal)Úrefresh_customer_online_status)ÚCustomerÚDeviceCredentialÚMikrotikDeviceÚVoucher)ÚRealMikrotikAdapter)Údecrypt_secret_strictÚSecretDecryptionErrorc                ó$   € V ^8„  d   QhR\         /# ©é   Úreturn©Úint)Úformats   "Ú0/var/www/html/bestweb/app/core/status_monitor.pyÚ__annotate__r      s   € ÷ ñ ”sñ ó    c            	      ó|   €  \        ^
\        \        P                  ! RR4      4      4      #   \         d     ^# i ; i)é
   ÚBESTWEB_STATUS_POLL_SECONDSÚ30©Úmaxr   ÚosÚgetenvÚ
ValueError© r   r   Úpoll_intervalr#      s7   € ðÜ�2”sœ2Ÿ9š9Ð%BÀDÓIÓJÓKÐKøÜô Úðúó   ‚), ¬;º;c                ó$   € V ^8„  d   QhR\         /# r   r   )r   s   "r   r   r   &   s   € ÷ ñ œsñ r   c            	      ó|   €  \        ^\        \        P                  ! RR4      4      4      #   \         d     ^
# i ; i)é   ÚBESTWEB_VOUCHER_POLL_SECONDSÚ10r   r"   r   r   Úvoucher_poll_intervalr*   &   s7   € ðÜ�1”cœ"Ÿ)š)Ð$BÀDÓIÓJÓKÐKøÜô Úðúr$   c                ó>   € V ^8„  d   QhR\         R,          R\        /# ©r   ÚvalueNr   )Ústrr   )r   s   "r   r   r   -   s   € ÷ 'ñ '¤ d¥
ð '¬sñ 'r   c                óB  € \        T ;'       g    R4      P                  4       P                  4       pV'       g   ^ # RV9   Ed   ^ pTp\        P                  ! RV4      pV'       d,   \        VP                  ^4      4      pVP                  ^4      pVP                  R4      p V Uu. uF  p\        \        V4      4      NK  	  pp\        T4      ^8  d   TP                  ^ ^ 4       K$  \        T4      ^8¼  d   Y'^ ,          ,          pTR
R pTR,          T^ ,          R,          ,           T^,          ^<,          ,           T^,          ,           # \        P                  ! RV4      pV'       du   \        VP                  4       4      '       dV   R VP                  4        4       w  ršr¼pV	R	,          V
R,          ,           VR,          ,           V^<,          ,           V,           #  \        \        V4      4      # u upi   \         d     ^ # i ; i  \         d     ^ # i ; i)ztParse RouterOS uptime/limit-uptime into seconds.

Accepts 1d02:03:04, 02:03:04, 3h04m, 2w, 5m, 45s and bare digits.
Ú Ú:z^(\d+)d(.*)$Ni€Q i  z7(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?c              3   óJ   "  € T F  q'       d   \        V4      M^ x € K  	  R# 5i)é    Nr   )Ú.0Úgs   & r   Ú	<genexpr>Ú'parse_uptime_seconds.<locals>.<genexpr>N   s   é € ÐD±^°¦1œ˜Qœ¨!Ô+³^ùs   ‚!#i€:	 éýÿÿÿ)r.   ÚstripÚlowerÚreÚmatchr   ÚgroupÚsplitÚfloatr!   ÚlenÚinsertÚ	fullmatchÚanyÚgroups)r-   ÚrawÚdaysÚbodyÚ	day_matchÚpartsÚpÚnumsr<   ÚwÚdÚhÚmÚss   &             r   Úparse_uptime_secondsrQ   -   s¯  € ô
 ˆe�kˆk�rÓ
×
 Ñ
 Ó
"×
(Ñ
(Ó
*€CßÙð ˆc…zØˆØˆÜ—H’H˜_¨cÓ2ˆ	ßÜ�y—‘ qÓ)Ó*ˆDØ—?‘? 1Ó%ˆDØ—
‘
˜3“ˆð	Ù+0Ó1©5 a”Cœ˜a›–M©5ˆDÐ1ô �$‹i˜!ŒmØ�K‰K˜˜1ÖÜˆt‹9˜Œ>Ø˜•G�OˆDØ˜˜�9ˆDØ�e�|˜d 1�g¨�nÕ,¨t°A­w¸­|Õ;¸dÀ1½gÕEÐEä�LŠLØBÀCó€E÷ ”�U—\‘\“^×$Ò$ÙD°U·\±\´^ÓD‰ˆˆa�AØ�6�z˜A �IÕ%¨¨D­Õ0°1°rµ6Õ9¸AÕ=Ð=ðÜ”5˜“:‹Ðùò% 2øÜô 	Úð	ûô$ ô Úðús6   Â$G= Â(G8ÃG= Ç$H Ç8G= Ç=HÈHÈHÈHc                óL   € V ^8„  d   QhR\         R,          R\         R,          /# r,   )r   )r   s   "r   r   r   W   s"   € ÷ *ñ *”(˜T•/ð *¤h°¥oñ *r   c                ó¨   € V f   R# V P                   f!   V P                  \        P                  R7      # V P	                  \        P                  4      # )zGSQLite drops tzinfo, so treat naive timestamps as UTC before comparing.N)Útzinfo)rT   Úreplacer   ÚutcÚ
astimezone)r-   s   &r   Úas_utcrX   W   s>   € à‚}ÙØ‡|�|ÒØ�}‰}¤H§L¡Lˆ}Ó1Ð1Ø×ÑœHŸL™LÓ)Ð)r   c                óF   € V ^8„  d   QhR\         \        \        3,          /# r   )Údictr   r   )r   s   "r   r   r   `   s   € ÷ ñ ¤$¤sÔ,?Ð'?Õ"@ñ r   c              ƒ  ón  "  € V P                  \        \        4      P                  \        P                  R8H  4      4      G Rj  x€L
 P                  4       P                  4       p/ pV FÂ  pVP                  '       g   K  V P                  \        VP                  4      G Rj  x€L
 pV'       d   VP                  '       g   K[  \        VP                  RVP                   2R7      p\        VP                  VP                  TVP                   ;'       g    RR7      W#P"                  &   KÄ  	  V#  Lì L�5i)a  Build one adapter per active MikroTik device that has valid credentials.

Raises SecretDecryptionError when a stored secret cannot be decrypted with
the active encryption key, so the caller can surface it instead of silently
pushing ciphertext to the device.
TNzKredensial )Úlabelé"  )ÚhostÚusernameÚpasswordÚapi_port)Úexecuter   r
   ÚwhereÚ	is_activeÚscalarsÚallÚcredential_idÚgetr	   r   Úencrypted_secretÚnamer   r^   r_   ra   Úid)ÚdbÚdevicesÚadaptersÚmkÚcredr`   s   &     r   Ú_device_adaptersrq   `   sô   é € ð —Z‘ZÜŒ~Ó×$Ñ$¤^×%=Ñ%=ÀÑ%EÓFó÷ ç�wƒy—‘“ð ð 02€HÛˆØ××ÐÙØ—V‘VÔ,¨b×.>Ñ.>Ó?×?ˆß˜ŸŸ˜ÙÜ(Ø×!Ñ!¨;°t·y±y°kÐ)Bô
ˆô .Ø—‘Ø—]‘]ØØ—[‘[×(Ð( Dô	
ˆ—‘‹ñ ð €Oñ'ñ @ùs8   ‚AD5ÁD1ÁAD5Â#D3Â$D5Â0D5ÃAD5ÄD5Ä3D5c                ó   € V ^8„  d   QhRR/# ©r   r   Nr"   )r   s   "r   r   r   }   s   € ÷ Yñ Y $ñ Yr   c               ƒ  óÆ	  "  € \        4       ;_uu_4       GRj  x€L
 p \        P                  ! \        P                  4      pV P                  \        \        4      4      G Rj  x€L
 P                  4       P                  4       pV'       g    RRR4      GRj  x€L
  R#  \        V 4      G Rj  x€L
 p/ pRpTP                  4        Fˆ  w  rx \         P"                  ! TP$                  4      G Rj  x€L
 p	T	 U
u/ uF  qªR,          T
bK  	  up
YW&   T P'                  \(        T4      G Rj  x€L
 pTe   TP*                  T8w  d   Yn        RpK†  Kˆ  KŠ  	  T EF{  pTP.                  ;'       g    TP0                  ;'       g    RP3                  4       pTP4                  pT'       d"   TP'                  T/ 4      P'                  T4      MRp\7        TP8                  4      p\;        T;'       d    Yñ8*  4      pRpT'       db   \=        TP'                  R4      4      p\=        TP'                  R	4      4      ;'       g    \=        TP>                  4      pT^ 8”  d
   TT8¼  d   RpT'       dÄ   TP'                  R
4      '       g    \=        TP'                  R4      4      ^ 8”  dM   TP@                  R8X  d;   RTn         TPB                  ;'       g    TTn!        Rp\        PE                  RT4       MATP@                  R9  d1   TP@                  R8X  d    RTn         Rp\        PE                  RT4       T'       g   T'       g   EKè  T'       d   TP'                  T4      MRpT'       d3   T'       d+    \         P"                  ! TPF                  T4      G Rj  x€L
  T'       d   RMRp\        PE                  RTT4       T PI                  T4      G Rj  x€L
  RpEK~  	  T'       d   T PK                  4       G Rj  x€L
  RRR4      GRj  x€L
  R#  ELî EL§ ELu ELb  \         d   p\        P                  RT4       h Rp?i\         d     RRR4      GRj  x€L 
  R# i ; i ELku up
i  EL:  \         d     / YW&   \        P-                  RT4        EKÈ  i ; i EL  \         d    \        P-                  RT4        EL(i ; i Lô LÏ LÁ  + GRj  x€L 
 '       g   i     R# ; i5i)z9Sync voucher status/deletion against live RouterOS state.Nzvoucher reconcile stopped: %sFrj   Tz5voucher reconcile: RouterOS read failed for device %sr0   ÚuptimeÚlimit_uptimeÚactiveÚsoldÚusedz/voucher %s marked used (live RouterOS activity)z4voucher %s back to sold (no live activity on device)z"voucher %s: RouterOS delete failedÚexpiredÚduration_exhaustedz*voucher %s removed (%s) from DB and device)Ú	available)&r   r   Únowr   rV   rb   r   r   re   rf   rq   r   Ú_logÚerrorÚ	ExceptionÚitemsÚasyncioÚ	to_threadÚlist_hotspot_usersrh   r
   Úlast_sync_atÚwarningr_   Úcoder9   Ú	device_idrX   Ú
expires_atÚboolrQ   rv   ÚstatusÚused_atÚinfoÚdelete_hotspot_userÚdeleteÚcommit)rl   r}   Úvouchersrn   ÚexcÚremote_by_deviceÚchangedrˆ   ÚadapterÚremote_rowsÚrowÚdeviceÚvr_   Úremoter‰   rz   r{   Úused_secondsÚlimit_secondsÚreasons                        r   Úreconcile_vouchersrž   }   sé  é € ä ×"×"Ô" bÜ�lŠlœ8Ÿ<™<Ó(ˆØŸ*™*¤V¬G£_Ó5×5×>Ñ>Ó@×DÑDÓFˆßØ÷	 #×"Ñ"ð	Ü-¨bÓ1×1ˆHð 8:ÐØˆØ"*§.¡.Ö"2ÑˆIð#Ü$+×$5Ò$5°g×6PÑ6PÓ$Q×Q�ÙKVÓ.WÉ;ÀC°6­{¸CÒ/?É;Ñ.WÐ Ñ+ð  "Ÿv™v¤n°iÓ@×@�ØÒ%¨&×*=Ñ*=ÀÔ*DØ*-Ô'Ø"’Gñ +EÑ%ñ #3ô" ˆAØŸ
™
×2Ð2 a§f¡f×2Ð2°×9Ñ9Ó;ˆHØŸ™ˆIßJSÐ%×)Ñ)¨)°RÓ8×<Ñ<¸XÔFÐY]ˆFô   §¡Ó-ˆJÜ˜:×;Ð;¨*Ñ*;Ó<ˆGð "'ÐßÜ3°F·J±J¸xÓ4HÓI�Ü 4°V·Z±ZÀÓ5OÓ P× xÐ xÔThÐij×iwÑiwÓTx�Ø  1Ô$¨¸Ô)FØ)-Ð&÷ Ø—:‘:˜h×'Ò'Ô+?ÀÇ
Á
È8Ó@TÓ+UÐXYÔ+YØ—x‘x 6Ô)Ø#)˜œØ$%§I¡I×$4Ð$4°˜œ	Ø"&˜ÜŸ	™	ØMÈxôøð —X‘X ^Ô3Ø—x‘x 6Ô)Ø#)˜œØ"&˜ÜŸ	™	ØRØ$ô÷
 ×,Ò,ß5>˜(Ÿ,™, yÔ1ÀD�ßŸxðÜ%×/Ò/°×0KÑ0KÈXÓV×VÐV÷
 '.™Ð3G�Ü—	‘	Ø@À(ÈFôð —i‘i “l×"Ð"Ø“ñe ÷h Ø—)‘)“+×Ð÷o #×"Ô"â5ò #ò 2øÜ$ô 	Ü�J‰JÐ6¸Ô<ØûÜô 	Ø÷ #×"Ò"ð	úò RùÚ.Wò Aøô ô ð /1Ð Ñ+Ü—‘ØKÈY÷ð ð	úòj WøÜ$ô ÜŸ™Ø@À(÷ðúñ #ñ ÷o #×"×"Ó"üsá  ‚S!™PšS!�ASÁ#P
Á$'SÂSÂS!ÂPÂS!Â PÂ.PÂ/PÂ3SÃ#Q&Ã0Q
Ã1Q&Ã9Q
ÄQ&ÄSÄ)Q#
Ä*ASÅ,SÆ $SÆ%ASÇ+SÇ>:SÈ9-SÉ'SÉ>A	SËA)SÌ2SÌ=SÍSÍ SÍ)$RÎR
ÎRÎSÎ/SÏ	R>
Ï
SÏSÏ0S Ï1SÏ5S!Ð SÐS!Ð
SÐS!ÐPÐQÐP5Ð5QÑQÑSÑS!ÑQÑS!ÑQÑSÑQ&ÑQ&Ñ#SÑ&%R	ÒSÒR	ÒSÒRÒ!R;	Ò7SÒ:R;	Ò;SÓ SÓS!ÓS	Ó
SÓ
S	ÓS	Ó	S!c                ó   € V ^8„  d   QhRR/# rs   r"   )r   s   "r   r   r   Ù   s   € ÷ -ñ -˜Dñ -r   c               ƒ  óX  "  €   \        4       ;_uu_4       GRj  x€L
 p V P                  \        \        4      P	                  \        P
                  R8H  \        P                  P                  R4      \        P                  P                  R4      \        P                  P                  R4      4      4      G Rj  x€L
 P                  4       P                  4       pV F  p \        W 4      G Rj  x€L
  K  	  RRR4      GRj  x€L
  \        P"                  ! \%        4       4      G Rj  x€L
  EK/   EL Ly LF  \         d    T P                  4       G Rj  x€L 
   K‚  i ; i L_  + GRj  x€L 
 '       g   i     Lv; i  \        P                    d    h \         d     L›i ; i L}5i)zMKeep billing status aligned with live OLT/MikroTik state and expire vouchers.NF)r   rb   r   r   rc   Úis_archivedÚpppoe_usernameÚis_notÚmikrotik_device_idÚolt_device_idre   rf   r   r€   Úrollbackr‚   ÚCancelledErrorÚsleepr#   )rl   Ú	customersÚcustomers      r   Úmonitor_loopr«   Ù   s>  é € à
ð	Ü(×*×*Ô*¨bØ#%§:¡:Üœ8Ó$×*Ñ*Ü ×,Ñ,°Ñ5Ü ×/Ñ/×6Ñ6°tÓ<Ü ×3Ñ3×:Ñ:¸4Ó@Ü ×.Ñ.×5Ñ5°dÓ;ó	ó$÷ ÷ ‘7“9ŸS™S›Uð ó !*�Hð,Ü<¸XÓJ×JÒJñ !*÷ +×*ô$ �mŠmœM›OÓ,×,Ó,ò% +ññ KøÜ$ô ,Ø Ÿk™k›m×+Ô+ð,ú÷ +×*×*Ò*ûô ×%Ñ%ô 	ØÜô 	Ùð	úá,ùsæ   ‚F*…F ›D2œF ŸBE&Â;D5Â<%E&Ã"D9Ã0D7
Ã1D9Ã5E&Ã9F ÄE$ÄF Ä	!F*Ä*F(Ä+F*Ä2F Ä5E&Ä7D9Ä9E!	ÅEÅE!	ÅE&Å E!	Å!E&Å$F Å&E?	Å,E/Å-
E?	Å8E?	Å:F Å>F*Å?F ÆF%Æ!F%Æ"F*Æ$F%Æ%F*c                ó   € V ^8„  d   QhRR/# rs   r"   )r   s   "r   r   r   ò   s   € ÷ 	5ñ 	5 Dñ 	5r   c               ƒ  ó   "  €   \        4       G Rj  x€L
  \        P                  ! \        4       4      G Rj  x€L
  K<   L,  \        P                   d    h \         d    \        P                  R4        Lai ; i LC5i)zDFast lane voucher reconciliation so UI reflects RouterOS state live.Nzvoucher reconcile cycle failed)rž   r‚   r§   r€   r~   Ú	exceptionr¨   r*   r"   r   r   Úvoucher_monitor_loopr¯   ò   sf   é € à
ð	=Ü$Ó&×&Ð&ô
 �mŠmÔ1Ó3Ó4×4Ò4ñ 'øÜ×%Ñ%ô 	ØÜô 	=Ü�N‰NÐ;Ö<ð	=úá4ùsE   ‚A>…A ’?“A —!A>¸A<¹A>¿A ÁA9Á A9Á6A>Á8A9Á9A>c                ó$   € V ^8„  d   QhR\         /# r   )rZ   )r   s   "r   r   r   þ   s   € ÷ *ñ *¤$ñ *r   c                 ó@  aa€ ^ RI o^ RIHo VV3R lp  SP                  V ! 4       4      pRT'       d?   \        ;QJ d"    R T 4       F  '       d   K   RRT/# 	  R	RT/# ! R T 4       4       RT/# RRT/#   \         d   pRRR\        T4      R. /u Rp?# Rp?ii ; i)
zFReport whether live voucher reconciliation can actually reach devices.Nr   c               “   óh  <"  € S	! 4       ;_uu_4       GR j  x€L
 p V P                  \        \        4      P                  \        P                  R8H  4      4      G R j  x€L
 P                  4       P                  4       p. pV FÉ  pRVP                  RVP                  RVP                  ;'       g    RRR RRR	R /p \        V 4      G R j  x€L
 pVP                  VP                  4      pVf   R
VR	&   MFS
P                  VP                  4      G R j  x€L
 pVP                  VR&   RVR&   \!        V4      VR&    VP'                  V4       KË  	  TuuR R R 4      GR j  x€L
  #  ELL EL	 Lž LX  \"         d   p\%        T4      TR	&    R p?LQR p?ii ; i L5  + GR j  x€L 
 '       g   i     R # ; i5i)NTr˜   r^   Úportr]   Ú
credentialÚokFr   z)Kredensial tidak aktif / belum ditugaskanÚusers)rb   r   r
   rc   rd   re   rf   rj   r^   ra   rq   rh   rk   rƒ   r„   r_   r@   r€   r.   Úappend)rl   rm   Úreportro   Úitemrn   r•   Úrowsr’   Ú_SessionÚ_asyncios            €€r   ÚprobeÚ'voucher_reconcile_health.<locals>.probe  sc  øé € Ù—:—:”: ØŸZ™ZÜ”~Ó&×,Ñ,¬^×-EÑ-EÈÑ-MÓNó÷ ç‰w‹yŸ™›ð ð ˆFÛ�à˜bŸg™gØ˜BŸG™GØ˜BŸK™K×/Ð/¨4Ø  $Ø˜%Ø˜Tð�ð-Ü%5°bÓ%9×9�HØ&Ÿl™l¨2¯5©5Ó1�GØ’Ø(S˜˜Wšà%-×%7Ñ%7¸×8RÑ8RÓ%S×S˜Ø-4×-=Ñ-=˜˜\Ñ*Ø%)˜˜T™
Ü(+¨D«	˜˜Wšð —‘˜dÖ#ñ+ ð, ÷7 —:•:òñ  :ñ
  Tøô !ô -Ü$'¨£H�D˜–Mûð-ú÷1 —:—:“:üs¾   ƒF2—E#˜F2›AFÁE&ÁAFÂ1
FÂ<E-Ã
E)
Ã(E-Ã3FÃ4E-ÄE+
Ä&E-Ä9FÅF2ÅFÅF2Å&FÅ)E-Å+E-Å-F	Å8F	ÆFÆF	ÆFÆF2ÆF/	ÆFÆ
F/	Æ'F/	Æ)	F2rµ   Fr   rm   c              3   ó2   "  € T F  qR ,          x € K  	  R# 5i)rµ   Nr"   )r4   rM   s   & r   r6   Ú+voucher_reconcile_health.<locals>.<genexpr>&  s   é € Ð+¡7˜a�D—'’'£7ùs   ‚T)r‚   Úapp.core.databaser   Úrunr€   r.   rf   )r½   rm   r’   r»   r¼   s      @@r   Úvoucher_reconcile_healthrÃ   þ   s³   ù€ ãÝ?öð<?Ø—,‘,™u›wÓ'ˆð 	¯w�c‹cÑ+¡7Ó+�cŒcØ�7ðð ÚØ�7ðð ØÑ+¡7Ó+Ô+Ø�7ðð Ø<AØ�7ðð øô ô ?Ø�e˜W¤c¨#£h°	¸2Ð>Õ>ûð?ús   •A7 Á7BÂBÂBÂB)"Ú__doc__r‚   Úloggingr   r;   Ú	getLoggerr~   Ú
sqlalchemyr   r   r   rÁ   r   Úapp.core.onboardingr   Úapp.models.modelsr   r	   r
   r   Úapp.core.mikrotikr   Úapp.core.secretsr   r   r#   r*   rQ   rX   rq   rž   r«   r¯   rÃ   r"   r   r   Ú<module>rÌ      sw   ðñó Û Û 	Û 	à×ÒÐ2Ó3€å ß 'å /Ý >÷ó õ 2ß Iõõõ'õT*õõ:Yõx-õ2	5÷*r   