
    `j                    (   d Z ddlmZ ddlZddlmZ ddlmZ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 ddlmZ ddlmZmZ ddlmZ  ej<                  e      Z 	 	 	 	 	 	 	 	 ddZ!ddZ"	 	 	 	 	 	 	 	 	 	 ddZ#	 	 	 	 	 	 	 	 ddZ$y)u  Build Ross1000 daily movement payloads for a date range.

Business rules implemented here:
- One movement block per calendar day (including empty days)
- Arrivals = guests whose check_in_date == target_date
- Departures = guests whose check_out_date == target_date
- Overnight presence (check_in < day < check_out) is NOT a separate block
  in Ross1000 (unlike C59 presenze) — only arrivals and departures are reported
- struttura block is always present, even on empty days
- idswh is read from GuestStay — never generated here

Performance:
- Structure metadata fetched once
- All bookings for the full range fetched in a single query
- GuestStay records fetched in a single query and indexed by (booking_id, guest_id)
- Per-day filtering done in Python — zero N+1 queries
    )annotationsN)defaultdict)date	timedelta)Any)Booking)	GuestStay)Ross1000ValidationError)Ross1000GuestPayloadRoss1000MovementDayPayloadRoss1000StrutturaPayload)build_ross1000_guest_payload)build_struttura_payload)validate_date_rangevalidate_structure)	Structurec                    t        t        j                  j                  | d||      j	                  ddd      j                  d      j                  dd            S )	aH  Fetch all bookings that have any activity in [start_date, end_date].

    Includes:
    - Bookings with check_in_date in range (arrivals)
    - Bookings with check_out_date in range (departures)
    - Bookings that span the entire range (overnight presence)

    Uses select_related + prefetch_related to avoid N+1 queries.
    T)structure_idis_checked_incheck_in_date__ltecheck_out_date__gtepropertyproperty_type	structureguestscheck_in_dateid)listr   objectsfilterselect_relatedprefetch_relatedorder_by)r   
start_dateend_dates      4/backend/istat/ross1000/services/movement_builder.py_fetch_bookings_for_ranger'   +   s[     %' *	 	 	
 

O[	A		(	#	/4	(
 
    c                    | si S t         j                  j                  | d      j                  ddd      }|D ci c]  }|d   |d   f|d    c}S c c}w )u   Fetch all GuestStay records for the given bookings.

    Returns a dict keyed by (booking_id, guest_id) → idswh.
    Only records with a non-null idswh are included.
    F)booking_id__inidswh__isnull
booking_idguest_ididswh)r	   r   r    values)booking_idsstaysstays      r&   _fetch_idswh_indexr3   F   s     	$$" %  f\:w/ 
 D 
l	T*-.W=  s   Ac          	     ,   g }| D ]  }|r|j                   |k7  r|s|j                  |k7  r't        |j                  j	                               }|D ]}  }t        |dd      }||j                  |j                  |f      }	|	s"t        j                  d||j                         T	 t        ||j                  |	      }
|j                  |
         t        |      S # t        $ r,}t        j                  d||j                  |       Y d}~d}~ww xY w)z?Build guest payloads for arrivals or departures on a given day.r   Nu>   Ross1000: missing idswh for guest %s / booking %s — skipping)guestr,   r.   uF   Ross1000: guest validation failed — skipping guest %s booking %s: %s)r   check_out_dater   r   allgetattrgetr   loggerwarningr   appendr
   tuple)bookingstarget_dateidswh_index
is_arrivalpayloadsbookingr   r5   r-   r.   payloadexcs               r&   _build_day_guestsrF   Z   s    ,.H'//;>g44Cgnn((*+EudD1HOOWZZ$:;ETJJ
 6&zz (/  T ? + \JJ	 s   &)C	D'"DDc                   t        |        t        ||       t        | j                  ||      }|D cg c]  }|j                   }}t	        |      }t        |       }g }||k  r|D cg c]"  }|j                  k(  s|j                  k(  r|$ }	}t        |	|d      }
t        |	|d      }t        fd|D              }t        |j                  |j                  ||j                  |j                        }|j                  t!        ||
|             t#        d	      z  |k  rt$        j'                  d
t)        |      | j                  ||       |S c c}w c c}w )uh  Build one Ross1000MovementDayPayload per calendar day in [start_date, end_date].

    Empty days (no arrivals, no departures) are included with empty tuples
    and the struttura block still present — as required by the spec.

    Args:
        structure:   Structure model instance (already validated)
        start_date:  First day of the export range (inclusive)
        end_date:    Last day of the export range (inclusive)

    Returns:
        List of movement day payloads, one per calendar day, sorted by date.

    Raises:
        Ross1000ValidationError: On invalid inputs or missing structure data.
    )r   T)rA   Fc              3  ~   K   | ]4  }|j                   cxk  r|j                  k  rn n|j                  d 6 y w)N   )r   r6   property_id).0bcurrent_days     r&   	<genexpr>z&build_movement_days.<locals>.<genexpr>   s9      
#!+@0@0@@) |s   :=)codiceaperturacamere_occupatecamere_disponibililetti_disponibili)r   	strutturaarrivipartenzerI   )daysu=   Ross1000: built %d movement days for structure %s (%s → %s))r   r   r'   r   r3   r   r   r6   rF   sumr   rO   rP   rR   rS   r<   r   r   r:   infolen)r   r$   r%   all_bookingsrL   r0   r@   struttura_baserW   day_bookingsrU   rV   occupiedrT   rM   s                 @r&   build_movement_daysr_      s   , y!
H- -Y\\:xPL "..A144K.$[1K -yAN-/DK

! $
#!+-1A1A[1P | 	 

 #+{t
 %+{u

  
#
 
 -!((#,,$-@@,>>
	 	& #!		
 	ya((M 
!P KKGD	 Kq /
s   E!-'E&)r   intr$   r   r%   r   returnlist[Booking])r0   z	list[int]ra   dict[tuple[int, int], str])
r>   rb   r?   r   r@   rc   rA   boolra   z tuple[Ross1000GuestPayload, ...])r   r   r$   r   r%   r   ra   z list[Ross1000MovementDayPayload])%__doc__
__future__r   loggingcollectionsr   datetimer   r   typingr   bookings.modelsr   guests.modelsr	   istat.ross1000.exceptionsr
   &istat.ross1000.models.movement_payloadr   r   r   &istat.ross1000.services.guest_resolverr   *istat.ross1000.services.structure_resolverr   +istat.ross1000.validators.payload_validatorr   r   structures.modelsr   	getLogger__name__r:   r'   r3   rF   r_    r(   r&   <module>rv      s   $ #  # $  # # = 
 P N _ ' 
		8	$  	6(444 ,4
 4 &4nUU U 	U
 &Ur(   