+
    js                         ^ RI t ^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RIHt  ! R R4      t ! R R4      t	RR	 lt
]R
8X  d   ]
! RRR7       R# R# )    N)datetime)defaultdictc                   j   a  ] tR t^t o RtRtR t]R 4       t]R 4       t	R t
R tR tRR ltR	tV tR
# )ChangeTrackeruM   Compara extrações sucessivas e conserva alterações visíveis por 2 horas.c                P    \         P                  P                  VR 4      V n        R# )zalteracoes_recentes.jsonN)ospathjoin)self	directorys   &&$C:\Users\ESI FRANCE\Desktop\stock.py__init__ChangeTracker.__init__   s    GGLL,FG	    c                >    \         P                  ! V RR\        R7      # )uJ   Serialização estável, incluindo listas/dicionários aninhados do stock.FT)ensure_ascii	sort_keysdefault)jsondumpsstrvalues   &r   _normalizarChangeTracker._normalizar   s     zz%etSQQr   c                `    V f   R# \        V \        \        34      '       d   R# \        V 4      # )Nz(vazio)zdados atualizados)
isinstancedictlistr   r   s   &r   _textoChangeTracker._texto   s(    =edD\**&5zr   c                     \        V P                  R RR7      ;_uu_ 4       p\        P                  ! V4      uuRRR4       #   + '       g   i     R# ; i  \        \        P
                  3 d
    R/ R/ /u # i ; i)rutf-8encodingN	snapshotsevents)openr	   r   loadOSErrorJSONDecodeError)r   files   & r   	_carregarChangeTracker._carregar   s^    	3diiw774yy 8777--. 	3Xr22	3s-    A A
A A	A A !A;:A;c           	         \        V P                  R RR7      ;_uu_ 4       p\        P                  ! WR^R7       RRR4       R#   + '       g   i     R# ; i)wr$   r%   Fr   indentN)r)   r	   r   dump)r   stater-   s   && r   _guardarChangeTracker._guardar&   s6    $))S733tIIea@ 4333s   AA	c                X   V P                  4       pWP                  R/ 4      9   d   R#  \        VRRR7      ;_uu_ 4       p\        P                  ! V4      pRRR4       \        X\        4      '       d|   V Uu/ uF6  qfP                  R4      '       g   K  \        VP                  R4      4      VbK8  	  upVR,          V&   V P                  V4       \        R\        V4       RV R	24       R# R#   + '       g   i     L; iu upi   \        \        P                  3 d     R# i ; i)
uK   Usa uma exportação já existente como referência na primeira execução.r'   Nr#   r$   r%   idu-   🕘 Histórico de alterações iniciado com z registos de .)r.   
setdefaultr)   r   r*   r   r   getr   r6   printlenr+   r,   )r   dataset	json_pathr5   r-   recordsitems   &&&    r   inicializar_de_ficheiro%ChangeTracker.inicializar_de_ficheiro*   s     &&{B77
	iw774))D/ 8'4((:A/:A$XXd^-C'-'/k"7+ e$Ec'l^S`ah`iijkl ) 87/
 --. 		s;   D
  C2"D
 9DD59D
 2D	=D
 
D)(D)c                   \         P                  ! 4       P                  4       pV P                  4       pVP	                  R/ 4      pVP	                  R/ 4      pV Uu/ uF6  qP                  V4      '       g   K  \        VP                  V4      4      VbK8  	  p	pVP                  V4      p
V
f   WV&   V P                  V4       V# V	P                  4        EF%  w  rV
P                  V4      p. pVf   R.pM\        \        V4      \        V4      ,          4      pV F  pVP                  R4      '       d   K  V P                  VP                  V4      4      V P                  VP                  V4      4      8w  g   Ka  VP                  V RV P                  VP                  V4      4       RV P                  VP                  V4      4       24       K  	  V'       g   EK  RVRV/VP	                  V/ 4      V&   EK(  	  WV&   / pVP                  V/ 4      P                  4        FU  w  ppV P                  VVP                  R^ 4      ,
          ,
          pV^ 8  g   K9  W9   g   KA  / VCR	\!        V4      /CVV&   KW  	  VWq&   V P                  V4       . pV FU  pVP                  \        VP                  V4      4      4      p\#        V4      pV'       d   VVR
&   VP                  V4       KW  	  V# u upi )uI   Anota cópias dos registos com _alteracoes enquanto estas não expirarem.r'   r(   zNovo registo_: u    → atfields	remaining_alteracoes)r   now	timestampr.   r;   r<   r   r6   itemssortedset
startswithr   appendr    DURACAO_SEGUNDOSintr   )r   r?   rA   keyrL   r5   r'   r(   rB   currentprevious	record_idoldchangedrI   fieldactiveeventrJ   	annotatedcopys   &&&&                 r   analisarChangeTracker.analisar;   s   lln&&( $$["5	!!(B/8?Q88C=+3txx}%t+Q==) !(gMM% N&}}OI,,y)CG{)*C3t9 45#E'',, ''74;K;KDHHUZO;\\%4;;swwu~3N2OuUYU`U`aeaiaijoapUqTr'st	 $
 w=A3RY<Z!!'2.y9  / %' &

7B 7 = = ?Iu--uyyq7I1IJI1}!5$Ju$Jk3y>$Jy! !@ !e	DJJs488C=12E:D&+]#T"  Q Rs   K39K3)r	   Ni   )r9   )__name__
__module____qualname____firstlineno____doc__rS   r   staticmethodr   r    r.   r6   rC   r`   __static_attributes____classdictcell____classdict__s   @r   r   r      sX     W"H R R  3A". .r   r   c                   x   a  ] tR t^kt o RR ltR tR tR tR tR t	RR lt
R tRR
 ltR tR tRR ltRtV tR	# )StockAPIc           
        Wn         \        P                  ! 4       V n        R V n        \
        P                  P                  \
        P                  P                  R4      R4      V n	        RV n
        \
        P                  P                  \
        P                  P                  R4      R4      V n        V P                  4       V n        R^ R^ R^ R^ R	^ /V n        R # )
N	~/Desktopzdimo_token.jsonFzstock_cache.jsontotal	com_stock	sem_stockerros
cache_hits)base_urlrequestsSessionsessiontokenr   r	   r
   
expanduser
token_filetoken_expired
cache_file_load_cachecachestats)r   ru   s   &&r   r   StockAPI.__init__l   s     '')
'',,rww'9'9+'FHYZ # '',,rww'9'9+'FHZ[%%'
 QQ!

r   c                    \         P                  P                  V P                  4      '       d\   \	        V P                  RRR7      ;_uu_ 4       p\
        P                  ! V4      p\        R\        V4       R24       VuuRRR4       #  / #   + '       g   i     / # ; i  \         d   p\        RT 24        Rp?/ # Rp?ii ; i)z#Carrega cache de stocks consultadosr#   r$   r%   u   📦 Cache carregado:     peças já consultadasNu   ⚠️ Erro ao carregar cache: )
r   r	   existsr}   r)   r   r*   r=   r>   	Exception)r   fr   es   &   r   r~   StockAPI._load_cache   s    	9ww~~doo..$//3AAQ IIaLE23u:,>UVW  BA / 	 BA 	  	93A3788		9s:   .B#  B# 0B
B# B 	B#  B# #C.CCc                    \        V P                  RRR7      ;_uu_ 4       p\        P                  ! V P                  VRR7       RRR4       R#   + '       g   i     R# ; i  \
         d   p\        RT 24        Rp?R# Rp?ii ; i)z"Guarda cache de stocks consultadosr1   r$   r%   F)r   Nu   ⚠️ Erro ao guardar cache: )r)   r}   r   r4   r   r   r=   )r   r   r   s   &  r   _save_cacheStockAPI._save_cache   sa    	8doosW==		$**ae< >=== 	821#677	8s4    A$ $AA$ A!	A$ !A$ $B/BBc                v    \         P                  P                  V P                  4      '       d   \	        V P                  RRR7      ;_uu_ 4       p\
        P                  ! V4      pVP                  R4      V n        V P                  '       d\   V P                  P                  P                  RV P                  4       \        RV P                  R,           R24        R	R	R	4       R
#  R	R	R	4       \        R4       R#   + '       g   i     L; i  \         d   p\        RT 24        R	p?R# R	p?ii ; i)z4Carrega o token do ficheiro (partilhado com Dimo.py)r#   r$   r%   ry   zMX-Access-Tokenu   🔑 Token carregado: N   N...NTu&   ❌ Token não encontrado no ficheiro.Fu   ⚠️ Erro ao carregar token: )r   r	   r   r{   r)   r   r*   r<   ry   rx   cookiesrP   r=   r   )r   r   datar   s   &   r   
load_tokenStockAPI.load_token   s    	ww~~doo..$//3AAQ99Q<D!%'!2DJzzz,,001BDJJO 6tzz#6GsKL# BA " B :; BA  	3A378	s;   .D  D BD!D ,D D	D D8D33D8c                    V P                   pRV n        V P                  4       '       dA   V P                   V8w  d#   \        RV P                   R,           R24       R# \        R4       R# R# )un   
Recarrega o token do ficheiro após ser renovado pelo Dimo.py
Devolve True se o token for diferente do atual
Fu   🔄 Token renovado: r   r   Tu2   ⚠️ Token recarregado mas é igual ao anterior.)ry   r|   r   r=   )r   	old_tokens   & r   recarregar_tokenStockAPI.recarregar_token   s[    
 JJ	"??zzY&-djjo->cBCJKr   c                8    \        R4       RV n        RV n        R# )z*Trata erro 401 - marca token como expiradou#   ⚠️ Stock: Token expirado (401)!NT)r=   ry   r|   )r   s   &r   _handle_401StockAPI._handle_401   s    34
!r   c                x   V P                   '       g   \        R4       . # . p^ p\        ^dV4      p\        RV R24       \        V4      V8  Ed   \        RV RW4,            R24       RpRR	V P                    2R
RRRRRRR/p V P                  P                  V P                   R2VRVRRVRV//^R7      pVP                  ^8X  d   VP                  4       pRV9   d   \        RVR,           24       EMVP                  R/ 4      P                  R/ 4      p	V	P                  R. 4      p
V	P                  R^ 4      pVP                  V
4       \        R\        V
4       R\        V4       R\        W4       R 24       \        V4      V8  g   \        V4      V8  d   M]W4,          p\        P                  ! R!4       EK  VP                  R"8X  d   V P                  4        . # \        R#VP                   24        \        R&\        V4       24       V#   \         d   p\        R$T 24        R%p?K8  R%p?ii ; i)'u9   
Obtém a lista de todas as peças (sem stock detalhado)
u   ❌ Erro: Token não definido!u'   📥 A buscar lista de peças (limite: z)...u   📥 A buscar peças de z a r   a$  
            query parts($skip: Int, $take: Int) {
              part(
                skip: $skip
                take: $take
                order: [{code: ASC}]
                filter: {}
              ) {
                items {
                  id
                  code
                  name
                  manufacturerName
                  manufacturerReference
                  partFamilyName
                  alert
                  sharedData
                }
                totalCount
              }
            }
            AuthorizationBearer Content-Typeapplication/json
controllerpartactionr   tenantSERRATECz/graphqlquery	variablesskiptake)headersr   timeouterrorsu   ❌ Erro GraphQL: r   rN   
totalCountu   ✅ Obtidos z registos (total: /)333333?  u   ❌ Erro HTTP: u
   ❌ Erro: Nu   📊 Total de peças: )ry   r=   minr>   rx   postru   status_coder   r<   extendtimesleepr   r   )r   limit	all_itemsr   r   r   r   responseresultr   rN   total_countr   s   &&           r   get_all_partsStockAPI.get_all_parts   sF    zzz23I	37wdCD)nu$,TF#dk]#FGE0  74::,!7 2f&*G(<<,,}}oX.##fdFD%A  -  ''3.%]]_F6) 26(3C2DEF!::fb155fbAD HHWb1E"&((<";K$$U+LU4Fs9~FVVWX[\aXoWppqrs9~.#i.K2OLDJJsO))S0$$&IOH,@,@+ABC 	&s9~&678  
1#&'s2   A1H 7B'H H  "H #H H9 H44H9c                r   V P                   '       g   R# WP                  9   d3   V P                  R;;,          ^,          uu&   V P                  V,          # V P                   R2pRV/pRRV P                    2RRR	R
/p V P                  P                  W$V^R7      pVP                  ^8X  d!   VP                  4       pW`P                  V&   V# VP                  R8X  d   V P                  4        R# \        RVP                   RV 24       R#   \         d   p\        RT RT 24        Rp?R# Rp?ii ; i)u   
Obtém os detalhes de uma peça (incluindo stock)

GET /SERRATEC/api/Part/get?id=UUID

Devolve None se token expirou (para o Dimo.py renovar)
Nrt   z/SERRATEC/api/Part/getr9   r   r   r   r   r   r   )r   paramsr   r   u   ⚠️ Erro HTTP u    para peça u   ⚠️ Erro ao buscar peça rG   )ry   r   r   ru   rx   r<   r   r   r   r=   r   )r   part_idurlr   r   r   r   r   s   &&      r   get_part_detailsStockAPI.get_part_details  s.    zzz jj JJ|$)$::g&&56 wtzzl3.j
	||''VUW'XH##s*}}&*

7#%%,  ")(*>*>)?|G9UV 	0	A3?@	s%    AD !D 2D D6D11D6Nc                |   V'       d   VRV p\        V4      p^ p^ p^ p\        RV R24       \        RVR,          ^<,          R R24       \        R(4       . p\        V^4       EF  w  rV
P                  RR	4      pV
P                  R
R	4      pV'       g   K4  V P                  '       d   \        RV	 RV R24        EMV P                  V4      pV'       d   VP                  R. 4      pV P                  V4      p/ V
CRVRVP                  R/ 4      RVP                  R/ 4      P                  R^ 4      RVP                  R/ 4      P                  R^ 4      /CpVP                  V4       VP                  R^ 4      ^ 8  d   V^,          pMdV P                  R;;,          ^,          uu&   MDV P                  '       d    MV^,          pVP                  / V
CRR^ R^ R^ R. R^ /R/ R^ R^ /C4       V^,          pW,          ^ 8X  g   W8X  dM   \        RV	 RV RV	^d,          V,           RV RV RV P                  R,           24       V P                  4        WP                  9  g   EK  \        P                  ! R4       EK  	  V P                  4        \        R(4       \        R 4       \        R!V 24       \        R"V 24       \        R#V 24       \        R$V P                  R,           24       V P                  '       d   \        R%4       WPP                  R&   W`P                  R&&   WpP                  R'&   V# ))u  
Processa cada peça, obtendo o stock detalhado

Args:
    parts: lista de peças
    limit: número máximo de peças a processar (None = todas)
    save_interval: guardar cache a cada N peças

Returns:
    Lista de peças enriquecidas com stock
    Interrompe se token_expired = True
Nu   
🔄 A processar u%    peças para obter stock detalhado...u   ⏱️ Estimativa: ~r   z.1fz minutosr9    codeu"   ⚠️ Token expirado! A parar em r   u    peças processadas.
partStocksstockpartCriticalitystandardPricecost
globalWauprp   rr   
disponivel	reservadolojas	num_lojasu   📊 Progresso:  (u   %) | ✅ Com stock: u    | ❌ Erros: u    | 💾 Cache: rt   u   ✅ Processamento concluído!u      📦 Total processadas: u      ✅ Com stock: u      ❌ Erros: u      💾 Cache hits: u9      ⚠️ TOKEN EXPIRADO - Dimo.py deve renovar e retomarrq   rs   z<============================================================)r>   r=   	enumerater<   r|   r   _consolidar_stockrR   r   r   r   r   r   )r   partsr   save_intervalrp   processadasrq   rs   	resultadoir   r   r   detailsstocksstock_consolidadopart_enriquecidas   &&&&             r   process_parts_with_stock!StockAPI.process_parts_with_stockE  si    &5MEE
	#E7*OPQ$US[2%5c$:(CDh	 *GAhhtR(G88FB'D !!!:1#QugEYZ[ ++G4G \26 %)$:$:6$B!$$.%w{{3Db'I#W[["%E%I%I&RS%T '++lB"?"C"CFA"N$    !12$((!4q8NIJJ{+q0+ %%%
   ""gq,;7TVXcefg%r#Q !"  1K  A%(1UG2aeUl^CWXaWbbpqvpw  xG  HL  HR  HR  S_  H`  Ga  b  c  " jj(

3{ +@ 	h-/+K=9:"9+./ug&'$TZZ%=$>?@MO *

7"+

;#

7r   c                j   V'       g   R^ R^ R^ R. R^ /# ^ p^ p^ p. pV EFD  p\        VP                  R^ 4      ;'       g    ^ 4      p\        VP                  R^ 4      ;'       g    ^ 4      p\        VP                  R^ 4      ;'       g    ^ 4      p	W',          pW8,          pWI,          pVP                  R	/ 4      p
VP                  R
/ 4      pV^ 8  g
   V^ 8  g   K  TP                  RV
P                  RR4      RV
P                  RR4      RV'       d   VP                  RR4      MRRV'       d   VP                  RR4      MRRVRVRV	RVP                  RR4      /4       EKG  	  VP                  R RR7       RTRTRTRTR\	        V Uu. uF  qR,          ^ 8  g   K  VNK  	  up4      /# u upi )z?
Consolida os stocks de todas as lojas numa estrutura resumida
rp   r   r   r   r   quantityavailableQuantityreservedQuantitystorestorageLocationlojanamezSem lojaloja_codigor   r   localizacaolocalizacao_codigo
quantidadestock_managementstockManagementFc                     V R ,          # )r    )xs   &r   <lambda>,StockAPI._consolidar_stock.<locals>.<lambda>  s    <r   T)rU   reverse)floatr<   rR   sortr>   )r   r   rp   r   r   r   sqtdavailresvr   storage_locationls   &&           r   r   StockAPI._consolidar_stock  s    Qaa"k[\]]
	Aj!,112C!%% 3Q7<<1=E115::;DLEJIEE'2&E uu%6; Qw%!)EIIfj9!599VR#8!GW#3#7#7#C]_(N^*:*>*>vr*Jdf # %&.?(G	 	 4 	

0$
? U*UFAL/A2EaaFG
 	

 Gs   F0"F0c                <   V'       g   \        R4       R# . R"Op \        P                  ! \        P                  P	                  V4      RR7       \        VRRRR7      ;_uu_ 4       p\        P                  ! WCRR7      pVP                  4        V EF  pVP                  R/ 4      pVP                  R/ 4      pRVP                  RR4      RVP                  RR4      RVP                  RR4      RVP                  RR4      RVP                  RR4      RVP                  R^ 4      R	VP                  R^ 4      R
VP                  R^ 4      RVP                  R^ 4      RV'       d   VP                  RR4      MRRVP                  R^ 4      RVP                  RR4      /p	VP                  V	4       EK   	  RRR4       \        R\        V4       R24       \        R V 24       R#   + '       g   i     L8; i  \         d   p
\        R!T
 24        Rp
?
R# Rp
?
ii ; i)#z(Exporta para CSV (com stock consolidado)u   ❌ Sem dados para exportarFr   r   manufacturerNamemanufacturerReferencepartFamilyNamestock_totalstock_disponivelstock_reservador   criticidadeprecor9   Texist_okr1   r   z	utf-8-sig)newliner&   ;)
fieldnames	delimiterr   r   rp   r   r   r   Nu   ✅ Exportado u    peças para:      📄 u   ❌ Erro ao guardar CSV: )r   r   r   r   r   r   r  r  r   r  r  r9   )r=   r   makedirsr	   dirnamer)   csv
DictWriterwriteheaderr<   writerowr>   r   )r   r   filenamer	  r   writerpr   r  rowr   s   &&&        r   export_to_csvStockAPI.export_to_csv  s   /0

 	KK1DAhR+FF!CP""$AEE'2.E"#%%(92">K fb 1fb 1*AEE2Db,I/7NPR1S(!%%0@"*E%uyy!'<*EIIlA,F)599[!+D#UYY{A%>%k{vr'BWY!:aeeD"oC OOC(% 	 G0 N3u:,m<=HXJ'(5 GF6  	-aS12	s1   AG7 'EG$4.G7 $G4	/G7 7HHHc                h   \        RV 24       V'       g   \        R4       R# \        V4      p\        R V 4       4      pWE,
          p\        R 4      p\        \        4      pV F  p	V	P                  RR4      ;'       g    Rp
V	P                  RR	4      ;'       g    R
pV	P                  R/ 4      P                  R^ 4      ^ 8  pWz,          R;;,          ^,          uu&   V'       d   Wz,          R;;,          ^,          uu&   W;;,          ^,          uu&   K  	  V^ 8  d
   V^<,          M^ pRV R2pV^ 8  d   VRV R2,          pVRV R\        P                  ! 4       P                  R4       R2,          pV^ 8  d   VRV R2,          pVRV RV RV R\        V4       R2	,          p\        VP                  4       4       F%  p
Wz,          pVRV
 RV
 RVR,           R 2,          pK'  	  VR!,          pV'       g   VR",          pEMV EF  p	V	P                  R#R$4      pV	P                  R%R&4      pV	P                  RR4      ;'       g    Rp
V	P                  RR	4      ;'       g    R
pV	P                  R'R	4      ;'       g    R(pV	P                  R)R	4      pV	P                  R/ 4      pVP                  R^ 4      pVP                  R*^ 4      pVP                  R+. 4      pV	P                  R,4      pR	pR	pV'       dU   R-P                  VR.,          \        P                  ,           4      pR/P                  R0P                  VR1,          4      4      pV^ 8:  d   R2pR3pR4pM-V^8:  d   R5pR6pR7\	        V4       R82pMR9pR:pR;\	        V4       2pV'       d
   VR<,          pR	pV'       d   V Uu. uF  pVR=,          ^ 8  g   K  VNK  	  p pV '       d   R>pV R?,           FD  p!V!R@,          '       d
   V!R@,          MV!RA,          p"VRBV" RC\	        V!R=,          4       RD2,          pKF  	  \        V 4      ^8  d   VRE\        V 4      ^,
           RF2,          pVRG,          pTR	P                  . RHNV NRINV'       d   RJMR	 NRKNV^ 8  d   RLMRM NRNNV
 NRONV NRPNV NRPNV NRQNV NRRNV NRNV NRSNV NRTNV NRUNV NRVNV NRWNV NRXNV^ 8:  d   RYMR	 NRZN\	        V4       NR[NV
 NR\NV NR]N4      ,          pEK  	  VR^V R_V R`\        P                  ! 4       P                  R4       Ra2,          p \        P                   ! \        P"                  P%                  V4      RbRc7       \'        VRdReRf7      ;_uu_ 4       p#V#P)                  V4       RgRgRg4       \        Rh4       \        RiV 24       \        Rj\        P"                  P+                  V4       Rk24       Rb# u upi   + '       g   i     L\; i  \,         d.   p$\        RlT$ 24       ^ RgIp%T%P1                  4         Rgp$?$R# Rgp$?$ii ; i)mzGera HTML visual para o stocku   📝 A gerar HTML do stock: u*   ❌ Sem dados para gerar o relatório HTMLFc              3   v   "   T F/  qP                  R / 4      P                  R^ 4      ^ 8  g   K+  ^x  K1  	  R# 5i)r   rp   N)r<   ).0r  s   & r   	<genexpr>,StockAPI.gerar_html_stock.<locals>.<genexpr>  s0     S5aEE'2,>,B,B7A,NQR,R5s   (9
9c                      R ^ R^ /# )rp   rq   r   r   r   r   r   +StockAPI.gerar_html_stock.<locals>.<lambda>  s    K'Cr   r   u   Sem famíliar   r   zSem fabricanter   rp   rq   u   
<!DOCTYPE html>
<html lang="pt">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>📦 Stock de Peças (z
)</title>
z(    <meta http-equiv="refresh" content="z">
u  
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container { max-width: 1400px; margin: 0 auto; }
        
        .header {
            background: linear-gradient(135deg, #0f3460 0%, #16213e 100%);
            color: white;
            padding: 30px;
            border-radius: 20px;
            margin-bottom: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        .header h1 { font-size: 32px; margin-bottom: 10px; }
        .header .subtitle { opacity: 0.9; font-size: 16px; }
        .header .timestamp { opacity: 0.7; font-size: 13px; margin-top: 5px; }
        
        .refresh-info {
            background: rgba(255,255,255,0.15);
            padding: 6px 15px;
            border-radius: 20px;
            font-size: 13px;
            display: inline-block;
            margin-top: 8px;
        }
        .refresh-info .countdown { font-weight: bold; color: #ffeb3b; }
        
        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
            gap: 15px;
            margin-top: 20px;
        }
        .stat-item {
            background: rgba(255,255,255,0.15);
            padding: 15px;
            border-radius: 12px;
            text-align: center;
        }
        .stat-item .number { font-size: 28px; font-weight: bold; }
        .stat-item .label { font-size: 12px; opacity: 0.8; margin-top: 5px; }
        .stat-item.com-stock .number { color: #4caf50; }
        .stat-item.sem-stock .number { color: #ff9800; }
        
        .filters {
            background: white;
            padding: 15px 20px;
            border-radius: 12px;
            margin-bottom: 25px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.08);
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            align-items: center;
        }
        .filters label { font-weight: 600; color: #555; font-size: 14px; }
        .filters select, .filters input {
            padding: 8px 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
        }
        .filters input:focus, .filters select:focus {
            outline: none;
            border-color: #0f3460;
        }
        .btn-refresh {
            padding: 8px 16px;
            background: #0f3460;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 13px;
        }
        .filter-stats { margin-left: auto; font-size: 13px; color: #666; }
        
        .part-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
            gap: 20px;
        }
        
        .part-card {
            background: white;
            border-radius: 16px;
            overflow: hidden;
            box-shadow: 0 4px 15px rgba(0,0,0,0.08);
            border-left: 6px solid #0f3460;
            transition: all 0.3s;
        }
        .part-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 30px rgba(0,0,0,0.15);
        }
        
        .part-card.sem-stock {
            border-left-color: #ff9800;
            opacity: 0.85;
        }
        .part-card.com-stock {
            border-left-color: #4caf50;
        }
        .part-card.stock-baixo {
            border-left-color: #f44336;
        }
        
        .part-card-header {
            padding: 15px 20px 10px;
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            background: #fafafa;
        }
        .part-card-header .part-code {
            font-weight: 700;
            font-size: 15px;
            color: #0f3460;
            font-family: monospace;
        }
        .part-card-header .stock-badge {
            font-size: 13px;
            padding: 3px 12px;
            border-radius: 20px;
            font-weight: 700;
        }
        .stock-badge-ok { background: #d4edda; color: #155724; }
        .stock-badge-baixo { background: #fff3cd; color: #856404; }
        .stock-badge-zero { background: #f8d7da; color: #721c24; }
        
        .part-card-body { padding: 15px 20px 20px; }
        .part-card-body .part-name {
            font-size: 15px;
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
        }
        
        .part-info {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 8px;
        }
        .part-info-item {
            padding: 6px 10px;
            background: #f8f9fa;
            border-radius: 6px;
        }
        .part-info-item .label {
            font-size: 10px;
            color: #888;
            text-transform: uppercase;
            letter-spacing: 0.3px;
            font-weight: 600;
        }
        .part-info-item .value {
            font-size: 13px;
            color: #333;
            margin-top: 1px;
            font-weight: 500;
        }
        .part-info-item .value.code {
            font-family: monospace;
            font-size: 12px;
        }
        .part-info-item.full-width { grid-column: 1 / -1; }
        .part-info-item.stock { background: #e8f5e9; }
        .part-info-item.stock-zero { background: #ffebee; }
        
        .lojas-list {
            margin-top: 10px;
            padding: 10px;
            background: #f8f9fa;
            border-radius: 8px;
        }
        .lojas-list .title {
            font-size: 11px;
            color: #666;
            text-transform: uppercase;
            font-weight: 600;
            margin-bottom: 6px;
        }
        .loja-item {
            display: flex;
            justify-content: space-between;
            padding: 4px 0;
            font-size: 12px;
            border-bottom: 1px solid #e9ecef;
        }
        .loja-item:last-child { border-bottom: none; }
        .loja-item .loja-nome { color: #555; }
        .loja-item .loja-qtd { font-weight: 700; color: #0f3460; }
        
        .no-data {
            text-align: center;
            padding: 60px 20px;
            color: #666;
        }
        .no-data .icon { font-size: 64px; margin-bottom: 20px; }
        
        .footer {
            text-align: center;
            margin-top: 30px;
            padding: 20px;
            color: #666;
            font-size: 13px;
        }
        
        @media (max-width: 768px) {
            .part-grid { grid-template-columns: 1fr; }
            .part-info { grid-template-columns: 1fr; }
            .header h1 { font-size: 24px; }
            .filters { flex-direction: column; align-items: stretch; }
            .filter-stats { margin-left: 0; text-align: center; }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap;">
                <div>
                    <h1>📦 Stock de Peças</h1>
                    <div class="subtitle">Total de uS    peças no sistema</div>
                    <div class="timestamp">📅 Gerado em z%d/%m/%Y %H:%M:%Sz</div>
u   
                    <div class="refresh-info">
                        🔄 Atualização automática a cada <span class="countdown">z+</span> minutos
                    </div>
uJ  
                </div>
                <div style="margin-top: 10px;">
                    <button class="btn-refresh" style="background:white;color:#0f3460;"
                            onclick="location.reload()">🔄 Atualizar Agora</button>
                    <button class="btn-refresh" style="background:white;color:#0f3460;"
                            onclick="window.print()">🖨️ Imprimir / PDF</button>
                </div>
            </div>
            
            <div class="stats">
                <div class="stat-item">
                    <div class="number">u   </div>
                    <div class="label">📦 Total de Peças</div>
                </div>
                <div class="stat-item com-stock">
                    <div class="number">u   </div>
                    <div class="label">✅ Com Stock</div>
                </div>
                <div class="stat-item sem-stock">
                    <div class="number">u   </div>
                    <div class="label">⚠️ Sem Stock</div>
                </div>
                <div class="stat-item">
                    <div class="number">uu  </div>
                    <div class="label">📂 Famílias</div>
                </div>
            </div>
        </div>
        
        <div class="filters">
            <label>📦 Stock:</label>
            <select id="stockFilter" onchange="filterCards()">
                <option value="all">Todos</option>
                <option value="com">✅ Com Stock</option>
                <option value="sem">⚠️ Sem Stock</option>
            </select>
            
            <label>📂 Família:</label>
            <select id="familyFilter" onchange="filterCards()">
                <option value="all">Todas</option>
z                <option value="z">r   z)</option>
u1  
            </select>
            
            <label>🔍 Pesquisar:</label>
            <input type="text" id="searchInput" onkeyup="filterCards()" placeholder="Código, nome ou referência...">
            
            <button class="btn-refresh" onclick="clearFilters()" style="background:#f8f9fa;color:#333;">✖️ Limpar</button>
            
            <span class="filter-stats" id="filterStats">Mostrando <span id="visibleCount">0</span> de <span id="totalCount">0</span></span>
        </div>
        
        <div class="part-grid" id="partGrid">
u   
            <div class="no-data">
                <div class="icon">📭</div>
                <h2>Nenhuma peça encontrada</h2>
            </div>
        r   zN/Ar   zSem nomer   -r9   r   r   rK   uD   <div class="change-timer" data-change-until="{}">🔔 Alterado</div>rH   uJ   <div style="font-size:12px;color:#a55b00;margin-bottom:8px;">🔔 {}</div>u    • rI   z	sem-stockzstock-badge-zerou   ❌ Sem Stockzstock-baixozstock-badge-baixou   ⚠️ Baixo (r   z	com-stockzstock-badge-oku   ✅ z	 alterador   uD   <div class="lojas-list"><div class="title">📍 Localizações</div>:N   Nr   r   zm
                            <div class="loja-item">
                                <span class="loja-nome">z?</span>
                                <span class="loja-qtd">z*</span>
                            </div>zJ<div style="text-align:center;font-size:11px;color:#888;margin-top:4px;">+z outras</div>z</div>z#
            <div class="part-card z	" style="z>box-shadow: 0 0 0 3px #ff9800, 0 8px 20px rgba(255,152,0,.25);z"
                 data-stock="comsemz "
                 data-family="z "
                 data-search=" z{">
                <div class="part-card-header">
                    <div>
                        <div class="part-code">zm</div>
                    </div>
                    <div>
                        <span class="stock-badge z </span>
                        zx
                    </div>
                </div>
                    <div class="part-card-body">
                    z,
                    <div class="part-name">u   </div>
                    <div class="part-info">
                        <div class="part-info-item full-width">
                            <div class="label">🏭 Fabricante</div>
                            <div class="value">u   </div>
                        </div>
                        <div class="part-info-item full-width">
                            <div class="label">🔖 Referência</div>
                            <div class="value code">z^</div>
                        </div>
                        <div class="part-info-item stockz-zeroux   ">
                            <div class="label">📦 Stock Total</div>
                            <div class="value">u   </div>
                        </div>
                        <div class="part-info-item">
                            <div class="label">📂 Família</div>
                            <div class="value">zU</div>
                        </div>
                    </div>
                    z7
                </div>
            </div>
            ui   
        </div>
        
        <div class="footer">
            <p>📦 Relatório de Stock • Total: u	    peças (u    com stock) • uQ
  </p>
        </div>
    </div>
    
    <script>
        function filterCards() {
            const stockFilter = document.getElementById('stockFilter').value;
            const familyFilter = document.getElementById('familyFilter').value;
            const searchInput = document.getElementById('searchInput').value.toLowerCase().trim();
            const cards = document.querySelectorAll('.part-card');
            let visible = 0;
            
            cards.forEach(card => {
                const stock = card.dataset.stock || '';
                const family = card.dataset.family || '';
                const search = card.dataset.search || '';
                
                let show = true;
                
                if (stockFilter !== 'all' && stock !== stockFilter) {
                    show = false;
                }
                
                if (show && familyFilter !== 'all' && family !== familyFilter) {
                    show = false;
                }
                
                if (show && searchInput && !search.toLowerCase().includes(searchInput)) {
                    show = false;
                }
                
                if (show) {
                    card.style.display = 'block';
                    visible++;
                } else {
                    card.style.display = 'none';
                }
            });
            
            document.getElementById('visibleCount').textContent = visible;
            document.getElementById('totalCount').textContent = cards.length;
        }
        
        function clearFilters() {
            document.getElementById('stockFilter').value = 'all';
            document.getElementById('familyFilter').value = 'all';
            document.getElementById('searchInput').value = '';
            filterCards();
        }

        function updateChangeTimers() {
            document.querySelectorAll('.change-timer').forEach(function(timer) {
                const seconds = Math.max(0, Math.ceil(Number(timer.dataset.changeUntil) - Date.now() / 1000));
                const hours = Math.floor(seconds / 3600);
                const minutes = Math.floor((seconds % 3600) / 60);
                const secs = seconds % 60;
                timer.textContent = seconds ? '🔔 Alterado • ' + hours + 'h ' + minutes + 'm ' + secs + 's' : 'Atualize a página';
            });
        }
        
        document.addEventListener('DOMContentLoaded', function() {
            filterCards();
            updateChangeTimers();
            setInterval(updateChangeTimers, 1000);
        });
    </script>
</body>
</html>
        Tr  r1   r$   r%   Nu%   ✅ HTML do stock gerado com sucesso!r  u      📊 Tamanho: z bytesu!   ❌ Erro ao gerar HTML do stock: )r=   r>   sumr   rT   r<   r   rL   strftimerO   keysformatr   rS   r
   r   r  r	   r  r)   writegetsizer   	traceback	print_exc)&r   r   r  refresh_intervaltotal_partsrq   rr   familiasfabricantesr  familia
fabricante	tem_stockrefresh_minuteshtml_contentinfor   r   refr   r   	qtd_totalqtd_disponivelr   
alteracoeschange_badgechange_description
card_classbadge_class
badge_text
lojas_htmlr   lojas_com_stockr   locr   r   r*  s&   &&&&                                  r   gerar_html_stockStockAPI.gerar_html_stock  s   ,XJ78>?%j S5SS	+	CD!#&Aee,n=OOG126JJ:JJgr*..w:Q>Ig&!+&!+.!3.#q(#  5Eq4H*b0a" #. / aFGWFXX\]]L e4J 5@= A;;C<<>;R;RSf;g:h iMg g	R a !WWfVg h L 	 ) *5 6) *3 4) *3 4) *-X 81) )	V hmmo.G$D=gYb	QSTXY`TaSbbnooL / 	  	   L uuVU+uuVZ0%% 0.ASS^UU#5r:NN>N
ee3R8??C%%b/gr*!IIgq1	!&<!;		'2.UU=1
!%'"#i#p#p"4(=+I+II$L *v)|)|Z%9:*&
 >!,J"4K!0J!^!.J"5K#1#i.1A!CJ!,J"2K#'I'7!8J+-J  
27&O%Q1\?Q;Nqq%O&O&%k
$3B$7$7D9=m9L9L$}"5RVW]R^C& /99< >88;D<N8O7P Q#+& &J %8 /!3&,vwz  |K  xL  OP  xP  wQ  Q^  +_  _J"h.
 &! &! %# &!#-,&!.7&! }G8x  MO  8P&!P&!'01}e%@&!A&!  'i&!(&!  $f	&! %&	&! '+V	&! ,-	&! .1E	&!20	&! 15v&!62&! 3>&! ?A&! BL&!M&! &&!'&! ((&!),&!  -16!&! 20!&!( 1;|)&!(<5)&!0 69E1&!0:91&!4 ENQRNXZ8[5&!4\05&!8 14I/?9&!8@09&!@ 18yA&!@9A&!F  LG&!F!G&! &q @ 	 4 5@=	)Tdemeqeqese|e|  ~Q  fR  eS DS		H H	T	KK1DAhg66!% 7 9;HXJ'(%bggooh&?%@GHS 'PF 76  	5aS9:!		s>   .W!W!5AW9  W&AW9 &W6	1W9 9X1"X,,X1)ru   r   r}   rx   r   ry   r|   r{   )z https://prod.mydimomaintmx.cloud)i  )N2   ),  )rb   rc   rd   re   r   r~   r   r   r   r   r   r   r   r   r  rB  rh   ri   rj   s   @r   rm   rm   k   sO     
,
8"""[z(Tl\.
`,\W Wr   rm   Fc           	        \         P                  P                  R4      p\        R4       \        R4       \        R4       \	        4       pV'       db   \         P                  P                  VP                  4      '       d3   \         P                  ! VP                  4       / Vn        \        R4       VP                  4       '       g   \        R4       R# \        R\        VP                  4       R24       TP                  V '       d   T MRR	7      pV'       g   \        R
4       R# VP                  '       d   \        R4       R# VP                  VV ^2R7      p\        V4      P                  RV4      pVP                  '       Ed   \        R4       \        R4       V'       d   \         P                  P!                  VR4      p\         P                  P!                  VR4      p\         P                  P!                  VR4      pVP#                  WV4       \%        VRRR7      ;_uu_ 4       p	\&        P(                  ! WYR^R7       RRR4       VP+                  WXRR7       \        RV 24       \        RV 24       \        RV 24       R# \         P                  P!                  VR4      p\         P                  P!                  VR4      p\         P                  P!                  VR4      pVP#                  WV4       \%        VRRR7      ;_uu_ 4       p	\&        P(                  ! WYR^R7       RRR4       \        RV 24       VP+                  WXRR7       \        R4       \        R4       \        RV 24       \        RV 24       \        RV 24       \        R4       R#   + '       g   i     ELg; i  + '       g   i     L; i) u   
Função principal para obter o stock completo
Pode ser chamada do Dimo.py

Args:
    limit: número máximo de peças a processar (None = todas)
    forcar_atualizacao: se True, ignora o cache
ro   u   📦 EXPORTADOR DE STOCKu   🗑️ Cache apagado.u;   ❌ Token não encontrado. Faça login no Dimo.py primeiro.Fu   📦 Cache: r   i'  )r   u   ❌ Nenhuma peça encontrada.u/   ⚠️ Token expirou durante a busca de peças!)r   r   r   u-   ⚠️ Token expirou durante o processamento!z   Devolvendo dados parciais...z	stock.csvz
stock.jsonzstock_visual.htmlr1   r$   r%   r2   NrE  )r,  r  u   ✅ JSON guardado: u   ✅ PROCESSO CONCLUÍDO!TzF======================================================================zG
======================================================================)r   r	   rz   r=   rm   r   r}   remover   r   r>   r   r|   r   r   r`   r
   r  r)   r   r4   rB  )
r   forcar_atualizacaoDESKTOPr   r   parts_com_stockcsv_file	json_file	html_filer   s
   &&        r   obter_stock_completorN  +  s    gg  -G	(O	
$%	(OJEbggnnU-=-=>>
		%""#&'KL	LU[[)**A
BC ue%@E-. ?@ 44 5 O $G,55gOO =>/0 ww||G[9HWl;IW.ABI:iw771		/5K 8 ""?PS"THXJ'(HYK()HYK() ww||G[1HWl3IW&9:I	2	iw	/	/1		/5C 
0		{
+,	?L	/	
$%	HXJ
 	HYK
 !	HYK
 !	(O? 877$ 
0	/s   0N(N<(N9	<O	__main__)r   rH  )NF)r   r   r   r  rv   r   collectionsr   r   rm   rN  rb   r   r   r   <module>rQ     sT    	   
   #^ ^@} }@]@ zt> r   