o
    Hi%                     @   s  d dl 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
mZ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mZ ddlmZmZmZ ddlmZm Z m!Z!m"Z"m#Z# d dl$m%Z% dd Z&dd Z'dZ(dZ)G dd deZ*G dd deZ+G dd deZ,dd Z-G dd de
j.Z/G dd  d e
j.Z0G d!d" d"e
j.Z1G d#d$ d$ej2Z3G d%d& d&eZ4G d'd( d(eZ5G d)d* d*eZ6G d+d, d,eZ7G d-d. d.eZ8G d/d0 d0eZ9dS )1    N)cache)Count)timezone)viewsetsstatusgenerics)action)Response)APIView)IsAuthenticatedIsAdminUserAllowAny)MultiPartParser
FormParser   )RegisteredMemberAttendanceRecordScannerAssignment)RegisteredMemberSerializerAttendanceRecordSerializerScannerAssignmentSerializerScanBarcodeSerializerCSVUploadSerializer)Eventc           
      C   s   d}t | t |}}t ||  }t || }t |d d t |t | t |d d   }	|d t t |	t d|	  S )z1Return distance in metres between two GPS points.i6a    r   )mathradianssincosatan2sqrt)
lat1lng1lat2lng2Rp1p2dpdla r+   </var/www/html/smartRegister/backend/apps/attendance/views.py_haversine_m   s   8$r-   c                 C   s0   | j d}|r|dd  S | j ddS )NHTTP_X_FORWARDED_FOR,r   REMOTE_ADDRunknown)METAgetsplitstrip)requestxffr+   r+   r,   _get_client_ip    s   $r8   g[1g%䃞9@c                   @      e Zd ZdZegZdd ZdS )PublicActiveCheckInEventViewz4Returns all events currently open for self-check-in.c                 C   s.   t jjddddd}dd |D }t|S )NTF)self_checkin_enabled	is_activeis_archivedcategoryc              	   S   s6   g | ]}|j |j|j|j|jr|jjnd |jdqS ) )idname
event_typelocationcategory_nameradius_m)r@   rA   rB   rC   r>   self_checkin_radius_m).0er+   r+   r,   
<listcomp>2   s    	z4PublicActiveCheckInEventView.get.<locals>.<listcomp>)r   objectsfilterselect_relatedr	   )selfr6   eventsdatar+   r+   r,   r3   .   s   	z PublicActiveCheckInEventView.getN__name__
__module____qualname____doc__r   permission_classesr3   r+   r+   r+   r,   r:   *       r:   c                   @   r9   )PublicSelfCheckInViewz
    Public self-check-in with:
      - Geofence validation (GPS must be within radius of venue)
      - IP rate limiting (max 5 per IP per 10 minutes)
      - Package / category validation
      - One-time check-in per member per event
    c              	   C   sR  |j d}|j dpd }|j d}|j d}|r |s+tdddd	tjd
S t|}d| }t|d}|dkrItdddd	tjd
S t	||d d zt
jdj|dddd}	W n t
jyv   tdddd	tjd
 Y S w |d u s|d u rtdddd	tjd
S zt|t|}
}W n ttfy   tdddd	tjd
 Y S w |	jrt|	jnt}|	jrt|	jnt}|	jpd}tt|
|||}||krtdd|d| d| dd tjd
S ztjd!j|d"}W n tjy   tdd#d$d	tjd
 Y S w |jr*|	jr*|j|	jkr*tdd%d&|jj d'|	jj d(d)S |	jra|	jd*kra|jp9d }|	j |vratdd+d,|jpMd- d.|	j   d/|jp[d-|	jd0S t!jj"||	d1d2# }|rtdd|j$|j%d3|j%&d4 d5d6S t!jj'||	d1t() d d7}tdd|j$|jpd|j%d8|j$ d9d:tj*d
S );Nevent_idregistration_coder?   latitude	longitudeFz)Event and registration code are required.MISSING_FIELDS)successmessagecoder   self_checkin_rate_r      zJToo many check-in attempts. Please wait a few minutes before trying again.RATE_LIMITEDr   iX  r>   T)r@   r;   r<   r=   z8Self check-in is not currently available for this event.EVENT_UNAVAILABLEzKYour location is required. Please enable GPS/location access and try again.GPS_REQUIREDzInvalid GPS coordinates.GPS_INVALIDi  OUT_OF_RANGEzBYou must be at the event venue to self check-in. You appear to be zm away (maximum allowed: z$m). Please move closer to the venue.)r]   r_   
distance_mr^   event_categoryregistration_code__iexactzBRegistration code not found. Please check your code and try again.	NOT_FOUNDCATEGORY_MISMATCHzYou are registered under "" but this event belongs to "z+". Please check with the registration desk.)r]   r_   r^   ALLPACKAGE_MISMATCHzYour package "N/Az" does not include access to zE. Please visit the registration desk if you believe this is an error.)r]   r_   r^   package_namerB   CHECK_INmemberevent	scan_typezYou have already checked in at z%H:%M on %d %b %Y.)r]   already_checked_inmember_namecheck_in_timer^   ru   rv   rw   	scan_timerecorded_byz	Welcome, z%! Your attendance has been confirmed.)r]   ry   rz   rr   r{   r^   )+rO   r3   r5   r	   r   HTTP_400_BAD_REQUESTr8   r   HTTP_429_TOO_MANY_REQUESTSsetr   rJ   rL   DoesNotExistHTTP_404_NOT_FOUNDfloat
ValueError	TypeErrorself_checkin_latDEFAULT_VENUE_LATself_checkin_lngDEFAULT_VENUE_LNGrF   intr-   r   ri   r>   rA   rB   rr   uppertitler   rK   first	full_namer}   strftimecreater   nowHTTP_201_CREATED)rM   r6   rX   rY   rZ   r[   iprate_keyattemptsrv   latlng	venue_lat	venue_lngradiusdistanceru   	pkg_upperexistingrecordr+   r+   r,   postJ   s   















	
zPublicSelfCheckInView.postN)rQ   rR   rS   rT   r   rU   r   r+   r+   r+   r,   rW   @   s    rW   c                   @   r9   )PublicMemberLookupViewz@Public endpoint: look up member name/email by registration code.c                 C   sn   |j dd }|stddiS ztjj|d}td|j|jp!ddW S  tjy6   tddd	 Y S w )
Nr_   r?   foundFrj   T)r   r   emailzRegistration code not found.)r   r^   )	query_paramsr3   r5   r	   r   rJ   r   r   r   )rM   r6   r_   ru   r+   r+   r,   r3      s   
zPublicMemberLookupView.getNrP   r+   r+   r+   r,   r      rV   r   c                 C   sB   | j stj S tjj| j d}| jr| jdkr|j| jd}|S )a%  Return members eligible for an event based on category + package match.

    Rules (mirrors ScanBarcodeView package check):
    - event.event_type == 'ALL' (or blank): every member in the category qualifies.
    - otherwise: only members whose package_name contains the event_type string.
    )ri   ro   )package_name__icontains)r>   r   rJ   nonerK   rB   )rv   qsr+   r+   r,   get_members_for_event   s   
r   c                   @   s*   e Zd ZeZegZdZdd Zdd Z	dS )RegisteredMemberViewSetNc           
      C   sH  t jd }| jjd}| jjd}| jjd}| jjd}| jjd}|rWztjdj|d}t|j	d	d
d}|j
|d}W n tjyV   | }Y n	w |r_|j
|d}|rs|j
|d|j
|dB |j
|dB }|r{|j
|d}|r|rtjj
|ddj	dd
d}	|dkr|j
|	d}|S |dkr|j|	d}|S )Nri   category_idrX   searchpayment_statusattendedr>   r@   r@   Tflat)id__in)event_category_id)full_name__icontains)registration_code__icontains)email__icontains)payment_status__iexactrs   )rX   rw   	member_idyesno)r   rJ   rL   allr6   r   r3   r   r   values_listrK   r   r   r   exclude)
rM   r   r   rX   r   r   r   rv   eligible_idschecked_in_idsr+   r+   r,   get_queryset   sL   

z$RegisteredMemberViewSet.get_querysetc                 C      | j dv r	t gS t gS N)listretriever   r   r   rM   r+   r+   r,   get_permissions     
z'RegisteredMemberViewSet.get_permissions)
rQ   rR   rS   r   serializer_classr   rU   pagination_classr   r   r+   r+   r+   r,   r      s    !r   c                   @   s&   e Zd ZeZegZdd Zdd ZdS )AttendanceRecordViewSetc                 C   s`   t jddd }| jjd}| jjd}|r |j|d}|r.|j|d|j|dB }|S )	Nru   rv   r~   rX   r   rX   )member__full_name__icontains)$member__registration_code__icontains)r   rJ   rL   r   r6   r   r3   rK   )rM   r   rX   r   r+   r+   r,   r   &  s   z$AttendanceRecordViewSet.get_querysetc                 C   r   r   r   r   r+   r+   r,   r   1  r   z'AttendanceRecordViewSet.get_permissionsN)	rQ   rR   rS   r   r   r   rU   r   r   r+   r+   r+   r,   r   "  s
    r   c                   @      e Zd ZeZegZdd ZdS )ScannerAssignmentViewSetc                 C   sR   t jdd }| jjd}| jjd}|r|j|d}|r'|j|d}|S )Nuserrv   user_idrX   )r   r   )r   rJ   rL   r   r6   r   r3   rK   )rM   r   r   rX   r+   r+   r,   r   ;  s   z%ScannerAssignmentViewSet.get_querysetN)rQ   rR   rS   r   r   r   rU   r   r+   r+   r+   r,   r   7  rV   r   c                   @   r   )MyAssignmentsViewc                 C   s   t jj| jjdddS )NT)r   r<   rv   )r   rJ   rK   r6   r   rL   r   r+   r+   r,   r   J  s
   zMyAssignmentsView.get_querysetN)rQ   rR   rS   r   r   r   rU   r   r+   r+   r+   r,   r   F  rV   r   c                   @      e Zd ZegZdd ZdS )ScanBarcodeViewc              
   C   s  t |jd}| st|jtjdS |jd  }|jd }|j	dd}zt
jdj	|d}W n t
jyE   td	d
dtjd Y S w |jjsbtjj|j|dd }|sbtd	ddtjdS d }d }	z&tjdj	|d}|j|j|j|jr~|jjnd|jpd|jpddd}	W n
 tjy   Y nw |d u rtd	d	ddS |jr|jr|j|jkrtd	d	dd|jj d|jj d|	dS |jr|jdkr|jpd }
|j |
vrtd	d	dd|jpd d|j   d|	dS t!t"jj||dj#ddd }|d!v rd|vrtd	d	d"|$d#d$   d%|	d&S ||v rEt"jj	|||d'}|%|}tdd|j&||d(|'  d)|j(|	d*S t"jj)|||t*+ |jd+}|%|}d,d-d.d/}tdd	|j&|||	|d0|j(|	d*S )1NrO   r`   rY   rX   rw   rs   r>   r   FzEvent not found.r]   r^   T)r   rv   r<   z#You are not assigned to this event.ri   )rY   rq   
attendance)r   r   rY   ri   rr   r   sourcez9Registration code not found. Member is not in the system.)r]   already_attendedr^   z/Category mismatch: member is registered under "rn   z".)r]   r   package_mismatchr^   ru   ro   r?   z!Access denied. Member's package "z" does not include z.. Please advise member on correct entry point.)ru   rv   r   )SESSION	CHECK_OUTzCannot record _ u#    — member has not checked in yet.)r]   r   r^   ru   rt   z%Member has already been recorded for rx   )r]   r   attendance_record_idrw   attendance_statusr^   r}   ru   r|   zCheck-In recorded successfully.zSession Verification recorded.z Check-Out recorded successfully.)rs   r   r   zScan recorded.),r   rO   is_validr	   errorsr   r   validated_datar5   r3   r   rJ   rL   r   r   r   is_superuserr   rK   existsHTTP_403_FORBIDDENr   r   r   rY   ri   rA   rr   r   r>   rB   r   r   r   r   r   replaceget_attendance_statusr@   get_scan_type_displayr}   r   r   r   )rM   r6   
serializerrY   rX   rw   rv   has_assignmentru   member_datar   existing_typesr   r   r   scan_type_labelsr+   r+   r,   r   S  s   

	



zScanBarcodeView.postN)rQ   rR   rS   r   rU   r   r+   r+   r+   r,   r   P      r   c                   @   s"   e Zd ZegZeegZdd ZdS )UploadCSVViewc                 C   s  t |jd}| st|jtjdS |jd }|jd}ddl	m
} d }|rDz	|jj|d}W n |jyC   tdd	itjd Y S w z| d
}tt|}d}	d}
g }t|ddD ]g\}}|dd }|sx|d| d q`|dd |dd |dd |dd |dd |dd |ddpdd}|r||d< tjj||d\}}|r|	d7 }	q`|
d7 }
q`td|	|
|d|	 d|
 dd W S  ty } ztdt|itjdW  Y d }~S d }~ww )!Nr   r`   csv_filer   r   EventCategoryr   errorEvent category not found.z	utf-8-sigr   )startzRegistration Coder?   zRow z%: Missing registration code, skipped.Emailz	Full NameStatuszPayment StatuszPackage NamezPackage DescriptionzActivity Price)r   r   r   r   rr   package_descriptionactivity_priceri   rY   defaultsr   TzImport complete. z
 created, z	 updated.)r]   createdupdatedr   r^   )r   rO   r   r	   r   r   r   r   r3   apps.events.modelsr   rJ   r   r   readdecodecsv
DictReaderioStringIO	enumerater5   appendr   update_or_create	Exceptionstr)rM   r6   r   r   r   r   ri   decodedreadercreated_countupdated_countr   irowreg_coder   r   r   rH   r+   r+   r,   r     sh   
	



"zUploadCSVView.postN)	rQ   rR   rS   r   rU   r   r   parser_classesr   r+   r+   r+   r,   r     s    r   c                   @   r   )UndoAttendanceViewc                 C   s   zt jddj|d}W n t jy    tdditjd Y S w |jj	s4|j
|jkr4tdditjdS |jj}|jj}|  tdd	| d
| ddS )Nru   rv   r   r   zAttendance record not found.r`   z/You can only undo records you scanned yourself.TzAttendance for z at z has been removed.r   )r   rJ   rL   r3   r   r	   r   r   r   r   r~   r   ru   r   rv   rA   delete)rM   r6   	record_idr   rz   
event_namer+   r+   r,   r     s"   zUndoAttendanceView.deleteN)rQ   rR   rS   r   rU   r  r+   r+   r+   r,   r    r   r  c                   @   r   )DashboardStatsViewc                 C   s(  |j d}tjjddd}|r|j|d}|jddd}|jd djd	dd }tjj|d
	 }t
jj|ddd 	 }t
jj|ddd 	 }t
jj|ddd 	 }	t
jj|dd dt
jj|dd d	 }
|rt|| d dnd}g }|dD ]s}t|	 }t
jj|ddd 	 }t
jj|ddd 	 }t
jj|ddd 	 }t
jj|dd dt
jj|dd d	 }|rt|| d dnd}||j|j|j ||||||d	 qt||||	|
||	 |dS )NrX   FT)r=   r<   r   r@   r   )r>   r   )event_category_id__inrs   )event_id__inrw   ru   r   r   d   r   r   
start_date)rv   rw   )	r@   rA   datetotal_members
checked_insessionschecked_outfull_attendance
percentage)r  total_checked_intotal_sessionstotal_checked_outtotal_full_attendanceattendance_percentagetotal_eventsevent_stats)r   r3   r   rJ   rK   r   r   distinctr   countr   valuesorder_byintersectionroundr   r  r@   rA   r  r  r	   )rM   r6   rX   	events_qs	event_idscategory_idsr  r#  r$  r%  r&  attendance_pctr)  rv   members_countev_checked_inev_sessionsev_checked_outev_fullpctr+   r+   r,   r3   8  s   


zDashboardStatsView.getN)rQ   rR   rS   r   rU   r3   r+   r+   r+   r,   r  5  r   r  c                   @   r9   )EMSConfigViewzJReturns EMS integration config available to the frontend (no credentials).c                 C   s2   ddl m} t|jo|j}t||jg ddS )Nr   settings)AGM
CONFERENCETRAININGEXPOGALAWORKSHOP)
configuredbase_urlevent_types)django.confr<  boolEMS_API_USERNAMEEMS_API_PASSWORDr	   EMS_API_BASE_URL)rM   r6   r<  rC  r+   r+   r,   r3     s   zEMSConfigView.getN)rQ   rR   rS   rT   r   rU   r3   r+   r+   r+   r,   r:    rV   r:  c                   @   r9   )ImportFromEMSViewa1  
    Pull paid registrations from the external ICTAZ EMS API and upsert
    them as RegisteredMember records in this system.

    POST body:
        event_category_id  (int)   - our EventCategory pk to link members to
        ems_event_type     (str)   - EMS eventType param, e.g. 'AGM', 'CONFERENCE'
    c                 C   s\  ddl m} ddlm} ddlm} |jd}|jdpd }|s-t	d	d
it
jdS |s8t	d	dit
jdS |jr>|jsGt	d	dit
jdS z	|jj|d}W n |jyc   t	d	dit
jd Y S w z
| }||}	W n  ty }
 zt	d	dt|
 it
jdW  Y d }
~
S d }
~
ww t|	tst	d	dit
jdS d}d}d}|	D ]v}|dpd }|s|d7 }q|dpd}|dpd }|dpd |dpd ||dpd |dpd |dpd |||dpd |dpddd}tjj||d\}}|r|d7 }q|d7 }qt	d|j|t|	|||d t
jdS )!Nr   r;  r   r   )EMSApiClientr   ems_event_typer?   detailzevent_category_id is required.r`   zems_event_type is required.zeEMS API credentials are not configured. Set EMS_API_USERNAME and EMS_API_PASSWORD in the environment.)pkr   zFailed to fetch from EMS API: z(Unexpected response format from EMS API.registrationCodetrainingNamepackageOrActivityNamefullName	userEmailmembershipNumberdocumentNumberphoneNumberpaymentStatuspriceactive)r   r   ri   membership_numberdocument_numberphone_numberrr   r   r   r   r   r   T)r]   ri   rM  total_fetchedr   r   skipped)rF  r<  r   r   ems_servicerL  rO   r3   r5   r	   r   r   rH  rI  HTTP_503_SERVICE_UNAVAILABLErJ   r   r   fetch_paid_registrationsr	  r
  HTTP_502_BAD_GATEWAY
isinstancer   r   r  rA   lenHTTP_200_OK)rM   r6   r<  r   rL  r   rM  r>   clientmembers_dataexcr  r  skipped_countentryr  training_namerr   r   ru   r   r+   r+   r,   r     s   



zImportFromEMSView.postN)rQ   rR   rS   rT   r   rU   r   r+   r+   r+   r,   rK    s    rK  ):r  r  r   django.core.cacher   django.db.modelsr   django.utilsr   rest_frameworkr   r   r   rest_framework.decoratorsr   rest_framework.responser	   rest_framework.viewsr
   rest_framework.permissionsr   r   r   rest_framework.parsersr   r   modelsr   r   r   serializersr   r   r   r   r   r   r   r-   r8   r   r   r:   rW   r   r   ModelViewSetr   r   r   ListAPIViewr   r   r   r  r  r:  rK  r+   r+   r+   r,   <module>   sF    
 ,
 B]