
    MRj4                         d Z ddlZddlZddlmZ ddlmZ ddlmZ ddl	m
Z
 ddlmZ ddlmZmZmZ dd	lmZ  ej&                  e      Z G d
 d      Zy)u  
FullService — sincronização read-only do estoque no Full (Fulfillment ML) e
motor de custo real de armazenagem/inbound.

Fase 1:
  - sync_full_inventory(): p/ cada Ad Full, resolve inventory_id(s), lê o estoque
    (disponível / em trânsito / indisponível), calcula dias de cobertura e
    antiguidade (best-effort via operações), grava snapshot + série diária.
  - compute_full_costs(): casa o volume real do produto com a tabela de tarifas
    e grava os campos de armazenagem em ProductFinancialMetric — que ads.py já
    consome no cálculo de margem do modal do produto (custo REAL no lugar do
    estimado por faixa de dimensão).

NÃO escreve nada no Mercado Livre. Tudo auditável (payload cru salvo em full_inventory.raw).
    N)SessionLocal)settings)Ad)
OAuthToken)ProductFinancialMetric)FullInventoryFullStockDailyFullStorageTariff)MeliApiServicec                       e Zd ZddZd Zd Zedefd       Zede	de
fd	       Zd
 ZddefdZede	fd       Zed        Zd ZddefdZy)FullServiceNc                 r    |xs
 t               | _        |d u | _        t        | j                        | _        y )N)
db_session)r   db_owns_dbr   meli)selfr   s     G/var/www/hypershopcomercio.com.br/hyper-ai/app/services/full_service.py__init__zFullService.__init__   s+    &d
"dgg6	    c                 R    | j                   r| j                  j                          y y N)r   r   close)r   s    r   r   zFullService.close$   s    ==GGMMO r   c                     | j                   j                  t              j                  t        j                  dk(        j                         }|r|j                  r|j                  S t        j                  S )Nmercadolivre)	r   queryr   filterproviderfirstuser_idr   MELI_USER_ID)r   tokens     r   get_seller_idzFullService.get_seller_id(   sR    j)001D1D1VW]]_U]]== $$$r   payloadc                    t        | j                  d      xs d      }t        | j                  d      xs d      }t        | j                  d      xs d      }d}| j                  dg       xs g D ]8  }|j                  d      dk(  s|t        |j                  d      xs d      z  }: t        d||z
        }|dk(  r||z   |z   }||||fS )	zPNormaliza o /stock/fulfillment em (available, in_transit, not_available, total).available_quantityr   totalnot_available_quantitynot_available_detailstatustransferquantity)intgetmax)r$   	availabler'   not_avail_total
in_transitdnot_availables          r   _parse_stockzFullService._parse_stock1   s     $89>Q?	GKK(-A.gkk*BCHqI
3R8>B 	:AuuX*,c!%%
"3"8q99
	: A;<A:
*]:E*mU::r   adreturnc                 @    t        | dd       xs d}|r
|dkD  r|dz  S dS )N	sales_30dr   g      >@        )getattr)r6   s30s     r   _daily_saleszFullService._daily_salesA   s,    b+t,1"sQwd
7C7r   c                    |dk  ryt         j                  j                         }| j                  j	                  |||t        j
                  d      z
  j                         |j                         d      }g }|xs g D ]  }t        |j                  d      xs d      j                         d	k7  r3t        |j                  d
      xs( |j                  d      xs i j                  d
      xs d      }|j                  d      xs$ |j                  d      xs |j                  d      }	|	s|dkD  s|j                  t        |	      dd |f        |sy|j                  d d       d|d   d   }}
|D ]  \  }}|
|z  }
|}|
|k\  s n 	 |t         j                  j                  |      z
  j                  S # t        $ r Y yw xY w)u  
        Aproxima a antiguidade do lote mais velho ainda em estoque, reconstruindo
        FIFO a partir das recepções (inbound_reception). Best-effort: se a API de
        operações não responder, devolve None (sem sobretaxa de estoque antigo).
        r   N;   )daysINBOUND_RECEPTION)	seller_idinventory_id	date_fromdate_toop_typetype inbound_receptionr,   detaildate_createddate	date_time
   c                     | d   S Nr    )xs    r   <lambda>z0FullService._oldest_stock_days.<locals>.<lambda>c   s
    ! r   T)keyreverse)datetimerL   todayr   get_fulfillment_operations	timedelta	isoformatstrr.   lowerr-   appendsortfromisoformatr@   	Exception)r   rB   rC   r0   rX   opsrecsoqtydtcumoldest_dater3   qs                 r   _oldest_stock_dayszFullService._oldest_stock_daysF   s    >##%ii22l x11r::EEGOO%/B 3 D  	1A 155=&B'--/3FFaeeJ'WAEE(O,Ar+F+Fz+RWVWXC~&M!%%-M155;MBcAgSWSb\3/0	1 		nd	3d2hqk[ 	DAq1HCKi		
	HMM77DDJJJ 		s   +F> >	G
	G
include_ageingc                    | j                         }t        j                  j                         }| j                  j                  t              j                  t        j                  dk(        j                         }t        j                  dt        |       d       d}d}|D ]  }	 | j                  j                  |j                        }t!        j"                  |      }	|	rB|j$                  |j&                  |j(                  f}
|
|	k7  r|	\  |_        |_        |_        |dz  }t!        j*                  |      }|s>t        j-                  d|j                   d       | j                  j/                          |D ]  \  }}| j                  j1                  |      }|s%| j3                  |      \  }}}}| j5                  |      }|dkD  rt7        ||z  d      nd}|r| j9                  |||      nd}| j                  j                  t:              j=                  |      j?                         }|s't;        |      }| j                  jA                  |       |j                  |_!        |jD                  |_"        ||_#        ||_$        ||_%        ||_&        ||_'        ||_(        |r||_)        ||_*        | j                  j                  tV              j=                  ||	      j?                         }|s(tW        ||	      }| j                  jA                  |       |jD                  |_"        |j                  |_!        ||_$        ||_%        ||_&        |dz  } | j                  j/                           t        j                  d| d       t        |      |||dS # tX        $ rJ}| j                  j[                          t        j]                  d
|j                   d|        Y d}~Pd}~ww xY w)z?Sync Full inventory; the rate-limited ageing history is opt-in.Tz[Full] u    anúncios marcados como Full.r      z sem inventory_id no payload.N)rC   )rC   dayz[Full] falha ao sincronizar z: u   [Full] sync concluído: u    unidades de inventário.)ads_fullinventoriesdimensions_updatedageing_requested)/r#   rW   rL   rX   r   r   r   r   is_fullallloggerinfolenr   get_itemidr   extract_shipping_dimensions	length_mmwidth_mm	height_mmextract_inventory_idsdebugcommitget_fulfillment_stockr5   r=   roundrj   r   	filter_byr   addad_idskuvariation_idavailable_qtyin_transit_qtynot_available_qty	total_qtydays_of_stockoldest_stock_daysrawr	   ra   rollbackerror)r   rk   rB   rX   ads	processedrq   r6   item
dimensionscurrent	inv_pairsrC   r   r$   r0   r2   r4   r'   dsalesr   oldestrowdailyes                            r   sync_full_inventoryzFullService.sync_full_inventoryo   sq   &&(	##%ggmmB&&rzzT'9:>>@gc#hZ'EFG	 8	JB7Jyy))"%%0+GGM
!||R[[",,GG*,BL?bk2<*a/**@@F	 LL7255'1N!OPGGNN$2; $#.L,"ii==lKG" BFBSBST[B\?Iz=%!..r2FDJQJE)f*<a$@TXM\jT44YiXptF''--6@@l@[aacC+FC( "CI ffCG'3C$(1C%)3C&,9C)$)CM(5C%%06-%CG !GGMM.9CC%1u D >>Ceg   .Le TE* "EI"$%%EK*3E'+5E(.;E+NII$#L  k8	Jt 	.yk9RSTHY&8n^ 	^  J  ";BEE7"QCHIIJs    )CN&<G>N&&	O9/?O44O9c                     | j                   r\| j                  rP| j                  rDt        | j                         t        | j                        z  t        | j                        z  dz  S y )Ng    .A)r{   r|   r}   float)r6   s    r   _volume_literszFullService._volume_liters   sI    <<BKKBLL",,'%*<<uR\\?RRVaaar   c                     |D ]A  }t        |j                  xs d      }|j                  }| |k\  s.|| t        |      k  s?|c S  y rP   )r   min_volume_lmax_volume_l)volume_ltariffstlohis        r   _match_tariffzFullService._match_tariff   sM     	Aq~~*+BB2~2:E"I1E		
 r   c           	      	   | j                   j                  t              j                  t        j                  dk(        j                  t        j                        j                         }|st        j                  d       dddS i }| j                   j                  t              j                  t        j                  j                  d            j                         D ]-  }|j                  |j                  g       j                  |       / i }| j                   j                  t              j                  t        j                   dk(  t        j                  j                  d            j                         D ]  }|j                  |j                  |         t#        |      t#        |      z  }d}ddd}|D ]  }	|j%                  |	      xsM | j                   j                  t              j                  t        j                  |	k(        j'                         }|sg| j)                  |      }
|
t        j+                  d|	 d	       | j-                  |
|      }|st        j+                  d|	 d
|
dd       t/        |j0                  xs d      }t/        |j2                  xs d      }|j%                  |	g       }|D cg c]  }|j4                  |j4                   }}|D cg c]  }|j6                  s|j6                   }}|rt9        |      t;        |      z  }d}nt/        |j4                  xs d      }d}|rt=        |      nd}||xx   dz  cc<   t?        |t/        |      dz  z  |z   d      }d}tA        |jB                  xs d      }t/        |jD                  xs d      }|r||kD  rt?        ||z
  |z  |z  d      }| j                   j                  tF              jI                  |	      j'                         }|s'tG        |	      }| j                   jK                  |       ||_&        ||_'        ||_(        ||_)        tT        jT                  jW                         |_,        |dz  } | j                   j[                          t        j]                  d| d|d    d|d    d       d|i|S c c}w c c}w )uY  
        Aplica full_storage_tariffs sobre o volume REAL do produto e grava os 4 campos
        de armazenagem em ProductFinancialMetric (a costura com ads.py / modal do produto).

        Duas fontes de cobertura/antiguidade:
          - full_stock  : há snapshot do CD (full_inventory) — dias e antiguidade REAIS.
          - est_coverage: sem estoque do CD (leitura do Full ainda gated) — usa a cobertura
                          por vendas (ad.days_of_stock) e sem sobretaxa de estoque antigo.
        A tarifa é sempre REAL (tabela do usuário); só a cobertura pode ser estimada.
        TuP   [Full] Sem tarifas ativas em full_storage_tariffs — custo real não calculado.r   
no_tariffs)updatedreasonN)
full_stockest_coveragez[Full] SKU u$    sem dimensões — pula custo real.z volume z.1fzL sem faixa de tarifa.r      r   rm   g       @   r:   Z   g      @)r   z[Full] custo gravado em z SKUs (CD real: z, cobertura estimada: z).r   )/r   r   r
   r   activeorder_byr   rt   ru   warningr   r   isnot
setdefaultr^   r   rs   setr.   r   r   r   r   r   	daily_feeinbound_feer   r   sumrw   r/   r   r-   aged_days_thresholdaged_daily_factorr   r   r   daily_storage_feeinbound_freight_coststorage_coststorage_risk_costrW   utcnowlast_calculated_atr   rv   )r   r   by_skur	ad_by_skur6   skusr   countsr   r   tariffr   r   inv_rows	days_listoldest_listavg_dayssourcer   r   storage_riskthrfactormetrics                            r   compute_full_costszFullService.compute_full_costs   s6    ''-- 1299:K:R:RVZ:Z[X'445cce 	NNmn L99 }-44]5F5F5L5LT5RSWWY 	3AaeeR(//2	3 	''--#**2::+=rvv||D?QRVVX 	-B  ,	- 6{S^+ !15 0	Cs#Vtww}}R'8'?'?#'N'T'T'VB**2.H{3%/STU''':F{3%x~E[\]f..3!4I 2 2 7a8Kzz#r*H 3;ZQaoo>YZIZ8@X1ADWDW1..XKXy>C	N:% !1!1!7R8')4S%$F6NaN !eHo.C!D{!RTUVLLf006B7C633:s;F&3,$fsli%?&%H!LWW]]#9:DDDMSSUF/C8F#'0F$*5F'".F'3F$(0(9(9(@(@(BF%qLGa0	d 	.wi 8!!'!5 66LVTbMcLddfh 	i7-f--E [Xs   S	 S	4SSc                 P    | j                  |      }| j                         }||dS )uM   Roda o sync de estoque e, em seguida, o motor de custo. Usado no job diário.)rk   )stockcost)r   r   )r   rk   r   r   s       r   sync_and_costzFullService.sync_and_cost  s.    (((G&&(--r   r   )F)__name__
__module____qualname__r   r   r#   staticmethoddictr5   r   r   r=   rj   boolr   r   r   r   r   rQ   r   r   r   r      s    7
% ;d ; ; 8 8 8 8'RE^$ E^T 2  
  T.l.D .r   r   )__doc__loggingrW   app.core.databaser   app.core.configr   app.models.adr   app.models.oauth_tokenr   app.models.financialr   app.models.fullr   r	   r
   app.services.meli_apir   	getLoggerr   ru   r   rQ   r   r   <module>r      sH      * $  - 7 L L 0			8	$D. D.r   