Ë
    c{ÎjNG ã                   ób  — d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	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ZddlmZmZmZmZmZmZmZ ddlmZmZmZ ddlm Z 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,m-Z-m.Z.m/Z/ ddl0m1Z1m2Z2 ddl3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9m:Z:m;Z; ddl<m=Z=  ej|                  d«      Z?dZ@dZAdZB e«       ZCdôd„ZD G d„ de «      ZEdeFdeeF   deFfd„ZGeCj‘                  d«      deEdefd„«       ZIe G d„ d «      «       ZJd!d"œd#eeF   d$eeF   d%eFd&eKdeJf
d'„ZLd(eFdeFfd)„ZMd*eFdeFfd+„ZNg d,¢ZOd-eFfd.„ZPd/eFdeQeFeReQ   f   fd0„ZSd1eQdeReF   fd2„ZTd/eFdeReF   fd3„ZUdõd/eFd5eFdeRfd6„ZVd7eReQ   deQfd8„ZWd1eQdeQfd9„ZXd*eFd:eReF   deYfd;„ZZ G d<„ d=e «      Z[ G d>„ d?e «      Z\eCj»                  d@«       ed«       ed«      fd#eeF   d$eeF   fdA„«       Z^eCj‘                  d@«       ed«       ed«      fde[ded#eeF   d$eeF   fdB„«       Z_eCjÁ                  dC«       ed«       ed«      fd*eFde\ded#eeF   d$eeF   f
dD„«       Za G dE„ dFe «      ZbeCj»                  dG«       ed«       ed«      fd#eeF   d$eeF   fdH„«       ZceCjÉ                  dG«       ed«       ed«      fdebded#eeF   d$eeF   fdI„«       ZeeCj»                  dJ«       ed«       ed«      fd#eeF   d$eeF   fdK„«       ZfeCj»                  dL«       ed«       edMdN¬O«       ed«       ed«      fdPeeF   dQeFd#eeF   d$eeF   fdR„«       Zg G dS„ dTe «      ZheCjÁ                  dU«       ed«       ed«      fdVeFdehded#eeF   d$eeF   f
dW„«       Zid/eFdeReQ   fdX„ZjeCj»                  dY«       ed«       ed«      fd#eeF   d$eeF   fdZ„«       Zk G d[„ d\e «      Zl G d]„ d^e «      Zmd-eFdeFfd_„ZneCj‘                  dY«       ed«       ed«      fdelded#eeF   d$eeF   fd`„«       ZoeCjÁ                  da«       ed«       ed«      fdbeYdemded#eeF   d$eeF   f
dc„«       ZpeCjã                  da«       ed«       ed«      fdbeYded#eeF   d$eeF   fdd„«       ZreCjã                  de«       ed«       ed«      fdfeFded#eeF   d$eeF   fdg„«       ZseCj»                  dh«       eddi¬j«       ed«       ed«      fdedkeeF   d#eeF   d$eeF   fdl„«       ZteCj‘                  dm«       ed«       ed«      fded#eeF   d$eeF   fdn„«       ZudoZvdpZwd*eFdqeYfdr„Zxd*eFdeQfds„Zy G dt„ due «      Zz G dv„ dwe «      Z{eCj»                  dx«       ed«       ed«      fd#eeF   d$eeF   fdy„«       Z|eCj‘                  dx«       ed«       ed«      fdezded#eeF   d$eeF   fdz„«       Z}eCjÁ                  d{«       ed«       ed«      fdqeYde{ded#eeF   d$eeF   f
d|„«       Z~eCjã                  d{«       ed«       ed«      fdqeYded#eeF   d$eeF   fd}„«       ZeCj‘                  d~«       ed«       ed«       ed«      fdqeYded€ed#eeF   d$eeF   f
d�„«       Z€eCj»                  d‚«      d*eFdqeYfdƒ„«       Z�eCj»                  d„«       ed«       ed«       ed«      fd…eeF   d#eeF   d$eeF   fd†„«       Z‚ G d‡„ dˆe «      Zƒ G d‰„ dŠe «      Z„ e�j
                  d‹«      Z†d/eFdŒeReF   deReF   fd�„Z‡d/eFdfeFdeQfdŽ„ZˆeCj‘                  d„«       ed«       ed«      fdeƒded#eeF   d$eeF   fd�„«       Z‰eCjÁ                  de«       ed«       ed«      fdfeFde„ded#eeF   d$eeF   f
d�„«       ZŠd‘Z‹d’eQded“eeF   deFfd”„ZŒd/eFdfeFd•eFdkeFdee�   f
d–„ZŽeCj»                  d—«       ed«       ed«       ed˜«       eddi¬j«      fd*eFdfeFded™eYdšeFd•eFdkeeF   fd›„«       Z�eCj»                  dœ«       ed˜«       ed«       eddi¬j«       ed«       ed«      fded•eFdPeeF   dkeeF   d#eeF   d$eeF   fd�„«       Z�džeJdeeF   fdŸ„Z‘eCj»                  d «       ed«       ed«       ed«       ed«       ed¡d¢¬£«       edd¬¤«       ed«       ed«      fd¥eeF   d¦eeF   d§eeF   d¨eeF   d©eYdªeYd#eeF   d$eeF   fd«„«       Z’eCj»                  d¬«       ed«       ed«       ed«       ed«       ed«       ed«      fd¥eeF   d¦eeF   d§eeF   d¨eeF   d#eeF   d$eeF   fd­„«       Z“deFfd®„Z”eCj»                  d¯«       ed«       ed«       ed«      fdPeeF   d#eeF   d$eeF   fd°„«       Z•eCj»                  d±«       ed«       ed«       ed«      fdPeeF   d#eeF   d$eeF   fd²„«       Z–eCj»                  d³«       ed«       ed«      fd#eeF   d$eeF   fd´„«       Z—eCj»                  dµ«       ed«       ed«      fd#eeF   d$eeF   fd¶„«       Z˜eCj»                  d·«       ed«       ed«      fd#eeF   d$eeF   fd¸„«       Z™eCj»                  d¹«       ed«       ed«      fd#eeF   d$eeF   fdº„«       ZšeCj»                  d»«       ed«       ed«      fd#eeF   d$eeF   fd¼„«       Z›eCj»                  d½«       ed«       ed«      fd#eeF   d$eeF   fd¾„«       ZœeCj»                  d¿«       eddÀdÁ¬Â«       edÃdÀdÄ¬Å«      fdÆeFdÇeYfdÈ„«       Z� G dÉ„ dÊe «      ZžeCj»                  dË«       ed«       ed«      fd#eeF   d$eeF   fdÌ„«       ZŸeCjÉ                  dË«       ed«       ed«      fdežded#eeF   d$eeF   fdÍ„«       Z eCj»                  dÎ«       ed«       ed«      fd#eeF   d$eeF   fdÏ„«       Z¡eCjÉ                  dÎ«       ed«       ed«      fdeQded#eeF   d$eeF   fdÐ„«       Z¢dÑZ£dÒeeF   ddfdÓ„Z¤eCj»                  dÔ«       ed«       ed«      fd#eeF   d$eeF   fdÕ„«       Z¥eCj»                  dÖ«       edd×¬O«       edd×¬O«       eddØ¬O«       eddØ¬O«      d edÁdÀdÙ¬Å«       ed«       ed«      fd§eeF   d¨eeF   dÚeeF   dÛeeF   dÜeeF   d©eYd#eeF   d$eeF   fdÝ„«       Z¦ G dÞ„ dße «      Z§eCj‘                  dà«       ed«       ed«      fde§ded#eeF   d$eeF   fdá„«       Z¨eCjã                  dâ«       ed«       ed«      fdfeFded#eeF   d$eeF   fdã„«       Z©eCjã                  dä«       ed4«       ed«       ed«      fdedÒeFd#eeF   d$eeF   fdå„«       Zªd/eFdeQfdæ„Z«eCj»                  dç«       ed«       ed«      fd#eeF   d$eeF   fdè„«       Z¬eCj‘                  dç«       ed4«       edé«       ed«       ed«      fdedÒeFdêeKd#eeF   d$eeF   f
dë„«       Z­eCjã                  dì«       ed«       ed«      fdÜeFdfeFdíeFded#eeF   d$eeF   fdî„«       Z®eCj»                  dï«       ed«       ed«      fd#eeF   d$eeF   fdð„«       Z¯eCj‘                  dñ«       ed«       ed«      fdòeFded#eeF   d$eeF   fdó„«       Z°y)öu«  
Meeconic Portal Backend â€” module admin / Ã©coles (multi-Ã©cole).

Authentification (JWT signÃ©) :
  POST  /api/admin/login                 {password, school?} â†’ rÃ´le operator (mot de passe ADMIN_PASSWORD)
                                         ou director (mot de passe de l'Ã©cole + code Ã©cole)

Ã‰coles (operator) :
  GET   /api/admin/schools               â†’ liste + chiffres clÃ©s
  POST  /api/admin/schools               â†’ crÃ©er une Ã©cole
  PATCH /api/admin/schools/{id}          â†’ renommer / (dÃ©s)activer / mot de passe directeur
  GET   /api/admin/system                â†’ Ã©tat du systÃ¨me (alertes, backup, organizerâ€¦)

Toutes les routes ci-dessous sont limitÃ©es Ã  UNE Ã©cole : le directeur est enfermÃ©
dans la sienne (jeton), l'opÃ©rateur choisit via l'en-tÃªte X-School.
  GET   /api/admin/settings, PUT /api/admin/settings
  GET   /api/admin/stats | /orders | /families
  GET|POST /api/admin/classes, PATCH|DELETE /api/admin/classes/{id}   â†’ classes de l'Ã©cole (saisies ici)
  POST  /api/admin/families, PATCH /api/admin/families/{code}       â†’ rattacher un code famille Ã  ses classes
  PATCH /api/admin/orders/{id}
  GET   /api/admin/export/printer.csv | labels | delivery-slip
  GET   /api/admin/audit | /audit/export.csv
  GET   /api/director/dashboard
  GET   /api/qr.svg                      â†’ QR code SVG (public)

Seules les commandes PAYÃ‰ES comptent dans les chiffres, exports et lots d'impression.
é    N)Ú	dataclass)ÚdateÚ	timedelta)ÚOptional)Ú	APIRouterÚFileÚHeaderÚHTTPExceptionÚQueryÚRequestÚ
UploadFile)ÚFileResponseÚPlainTextResponseÚResponse)Ú	BaseModelÚField)Úconfig)Úget_dbÚensure_columnÚTABLE_OPTIONS)Úenforce)Ú	client_ipÚconstant_time_eqÚ
jwt_secretÚverify_password)ÚutcnowÚ
utcnow_iso)Ú	ai_healthÚaudit_serviceÚcard_serviceÚmedia_serviceÚphoto_serviceÚphotographer_serviceÚprotection_serviceÚschool_service)Úgenerate_unique_codezmeeconic.adminÚHS256é   z/payment_status='paid' AND status != 'cancelled'Úreturnc                  óÚ  — t        j                  «        t        «       5 } | j                  dt        › �«       | j                  dt        › �«       | j                  dt        › �«       | j                  dt        › �«       t        | ddd«       | j                  d«       | j                  «        d d d «       t        j                  «        t        j                  d	«       y # 1 sw Y   Œ3xY w)
NaÔ  
        CREATE TABLE IF NOT EXISTS families (
            code        VARCHAR(32)  NOT NULL PRIMARY KEY,
            family_name VARCHAR(200) NOT NULL DEFAULT '',   -- legacy: schools may not send names, never filled again
            email       VARCHAR(200),
            created_at  VARCHAR(40),
            consent_at  VARCHAR(40),
            consent_ip  VARCHAR(64),
            school_id   VARCHAR(64),
            KEY idx_families_school (school_id)
        ) aÈ  
        CREATE TABLE IF NOT EXISTS students (
            id          INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            family_code VARCHAR(32)  NOT NULL,
            first_name  VARCHAR(100) NOT NULL DEFAULT '',   -- legacy, same reason
            last_name   VARCHAR(100) NOT NULL DEFAULT '',
            class_name  VARCHAR(100) NOT NULL,
            KEY idx_students_family (family_code),
            KEY idx_students_class (class_name)
        ) a<  
        CREATE TABLE IF NOT EXISTS school_classes (
            id            INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            school_id     VARCHAR(64)  NOT NULL,
            name          VARCHAR(40)  NOT NULL,
            student_count INT,
            UNIQUE KEY uq_school_class (school_id, name)
        ) a›  
        CREATE TABLE IF NOT EXISTS school_products (
            id         INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            school_id  VARCHAR(64)  NOT NULL,
            name_fr    VARCHAR(80)  NOT NULL,
            name_en    VARCHAR(80)  NOT NULL DEFAULT '',
            name_es    VARCHAR(80)  NOT NULL DEFAULT '',
            price      DOUBLE NOT NULL,
            physical   TINYINT NOT NULL DEFAULT 1,
            active     TINYINT NOT NULL DEFAULT 1,
            has_image  TINYINT NOT NULL DEFAULT 0,
            sort_order INT NOT NULL DEFAULT 0,
            created_at VARCHAR(40) NOT NULL,
            KEY idx_products_school (school_id)
        ) ÚordersÚ
class_namezVARCHAR(100)zËINSERT IGNORE INTO school_classes (school_id, name) SELECT DISTINCT f.school_id, s.class_name FROM students s JOIN families f ON f.code = s.family_code WHERE f.school_id IS NOT NULL AND f.school_id != ''zAdmin tables ready)r%   Úinit_school_tablesr   Úexecuter   r   Úcommitr   Úinit_audit_dbÚlogÚinfo)Úconns    ú /var/www/photos/backend/admin.pyÚinit_admin_dbr5   F   sð   € Ü×%Ñ%Ô'Ü	‹ð 3�TØ�‰ð 
ô ˆ/ð
ô 
	ð 	�‰ð 	ô ˆ/ð	ô 		ð 	�‰ð ô ˆ/ðô 	ð 	�‰ð ô ˆ/ðô 	ô 	�d˜H l°NÔCà�‰ðBô	Cð 	�‰Œ÷g3ôh ×ÑÔ!Ü‡H�HÐ!Õ"÷k3ð 3ús   ŸBC!Ã!C*c                   óN   — e Zd ZU  ed¬«      Zeed<    edd¬«      Zee   ed<   y)ÚLoginRequestéÈ   ©Ú
max_lengthÚpasswordNé<   ©Údefaultr:   Úschool)	Ú__name__Ú
__module__Ú__qualname__r   r;   ÚstrÚ__annotations__r?   r   © ó    r4   r7   r7   ‚   s&   … Ù SÔ)€HˆcÓ)Ù!¨$¸2Ô>€FˆH�S‰MÔ>rF   r7   Úroler?   c                 óš   — t        «       }| ||t        t        ¬«      z   dœ}|r||d<   t        j                  |t        «       t        ¬«      S )N©Úhours)rG   ÚiatÚexpr?   )Ú	algorithm)r   r   ÚJWT_TTL_HOURSÚjwtÚencoder   ÚJWT_ALGORITHM)rG   r?   ÚnowÚpayloads       r4   Ú_issuerT   ‡   sA   € Ü
‹(€CØ C°´iÄmÔ6TÑ0TÑU€GÙØ"ˆ�ÑÜ�:‰:�gœz›|´}ÔEÐErF   z/api/admin/loginÚbodyÚrequestc                 óv  — t        |«      }t        d|› �ddd|¬«       | j                  j                  «       }| j                  xs dj                  «       j                  «       }t        j                  rCt        |t        j                  «      r)t        j                  dd|d	¬
«       t        dd «      dd dœS |rUt        j                  |«      }|r>|d   r9t        ||d   «      r*t        j                  dd|d	|¬«       t        d|«      d|dœS t        j                  d|dd|xs d i¬«       t        dd¬«      ‚)Nzadmin-login:é   é„  zauth.login_bruteforce_blocked)Úmax_attemptsÚwindow_secondsÚaudit_actionÚ
ip_addressÚ z
auth.loginÚoperatorr2   )Úuser_idr]   Úseverity)ÚtokenrG   r?   ÚactiveÚdirector_password_hashÚdirector)r`   r]   ra   Ú	school_idzauth.login_failedÚwarningr?   )r]   ra   Údetailsé‘  zMot de passe incorrect.©Údetail)r   Úrate_limit_enforcer;   Ústripr?   Úlowerr   ÚADMIN_PASSWORDr   r   Ú	log_eventrT   r%   Úget_school_with_hashr   r
   )rU   rV   ÚipÚpwÚslugÚschs         r4   Úadmin_loginrv   �   s'  € ä	�7Ó	€Bä˜ b TÐ*¸È3Ø$CÐPRõTð 
�‰×	Ñ	Ó	€BØ�K‰KÒ˜2×$Ñ$Ó&×,Ñ,Ó.€Dä×ÒÔ!1°"´f×6KÑ6KÔ!LÜ×Ñ °jÈRÐZ`ÕaÜ 
¨DÓ1¸:ÐQUÑVÐVáÜ×1Ñ1°$Ó7ˆÙ�3�x’=¤_°R¸Ð=UÑ9VÔ%WÜ×#Ñ# L¸*ÐQSÐ^dÐptÕuÜ# J°Ó5¸zÐUYÑZÐZä×ÑÐ/¸BÈØ%-¨tª|°tÐ$<õ>ä
˜Ð$=Ô
>Ð>rF   c                   ó(   — e Zd ZU eed<   ee   ed<   y)ÚCtxrG   rf   N)r@   rA   rB   rC   rD   r   rE   rF   r4   rx   rx   ¨   s   … à
ƒIØ˜‰}ÔrF   rx   T©Úneed_schoolÚauthorizationÚx_schoolÚrolesrz   c                ó¬  — | r| j                  d«      st        dd¬«      ‚	 t        j                  | dd  t	        «       t
        g¬«      }|j                  d	«      }|r||vrt        d
d¬«      ‚d }|dk(  r>|j                  d«      }|rt        j                  |«      nd }|r|d   sOt        dd¬«      ‚|r@|j                  «       j                  «       }t        j                  |«      st        dd¬«      ‚|r|st        dd¬«      ‚t        ||¬«      S # t        j                  $ r t        dd¬«      ‚t        j                  $ r t        dd¬«      ‚w xY w)NzBearer ri   zToken manquant.rj   é   )Ú
algorithmsu)   Session expired â€” please sign in again.u&   Session invalide â€” reconnectez-vous.rG   é“  zAccess denied for this role.re   r?   rc   zSchool disabled or not found.é”  zSchool not found.é�  zSelect a school.)rG   rf   )Ú
startswithr
   rO   Údecoder   rQ   ÚExpiredSignatureErrorÚInvalidTokenErrorÚgetr%   Ú
get_schoolrm   rn   rx   )r{   r|   rz   r}   rS   rG   rf   ru   s           r4   Úget_ctxrŠ   ®   sI  € Ù × 8Ñ 8¸Ô CÜ˜CÐ(9Ô:Ð:ðRÜ—*‘*˜]¨1¨2Ð.´
³Ì-ÈÔYˆð
 �;‰;�vÓ€DÙ�˜UÑ"Ü˜CÐ(FÔGÐGà#€IØˆzÒØ—K‘K Ó)ˆ	Ù6?Œn×'Ñ'¨	Ô2ÀTˆÙ˜#˜hš-Ü Ð,KÔLÐLÙ	Ø—N‘NÓ$×*Ñ*Ó,ˆ	Ü×(Ñ(¨Ô3Ü Ð,?Ô@Ð@Ù™9Ü˜CÐ(:Ô;Ð;Ü�D IÔ.Ð.øô) ×$Ñ$ò UÜ˜CÐ(SÔTÐTÜ× Ñ ò RÜ˜CÐ(PÔQÐQðRús   ¢(D ÄA EÚsc                 óZ   — dj                  d„ t        j                  d| «      D «       «      S )Nr^   c              3   óR   K  — | ]  }t        j                  |«      d k7  sŒ|–— Œ! y­w)ÚMnN)ÚunicodedataÚcategory)Ú.0Úcs     r4   ú	<genexpr>z!_strip_accents.<locals>.<genexpr>Í   s$   è ø€ Òa˜Ä×AUÑAUÐVWÓAXÐ\`ÓA`”1Ñaùs   ‚' 'ÚNFD)Újoinr�   Ú	normalize)r‹   s    r4   Ú_strip_accentsr—   Ì   s$   € Ø�7‰7Ñaœk×3Ñ3°E¸1Ó=ÔaÓaÐarF   rf   c                 ó@   — t        | t        j                  |«      «      S )uc   CÃ³digo Ãºnico con el prefijo del colegio: MAR-4JC-IBD (887 millones de combinaciones por prefijo).)r&   r%   Úcode_prefix_for)r3   rf   s     r4   Únew_family_coderš   Ð   s   € ä ¤n×&DÑ&DÀYÓ&OÓPÐPrF   )ÚPSÚMSÚGSÚCPÚCE1ÚCE2ÚCM1ÚCM2Ú6EÚ5EÚ4EÚ3EÚnamec                 óò   — | j                  «       }t        t        «      D ]Q  \  }}|j                  |«      sŒt	        |«      t	        |«      k(  s|t	        |«         j                  «       rŒLd||fc S  dd|fS )Nr   é   )ÚupperÚ	enumerateÚ_LEVEL_ORDERr„   ÚlenÚisdigit)r§   rª   ÚiÚlvls       r4   Úclass_sort_keyr±   Ú   so   € Ø�J‰J‹L€EÜœLÓ)ò !‰ˆˆ3Ø×Ñ˜CÕ ¤c¨%£j´C¸³HÒ&<ÀEÌ#ÈcË(ÁO×D[ÑD[ÕD]Ø�q˜%�=Ò ð!ð ˆq�%ˆ=ÐrF   Úsidc                 óŒ   — i }| j                  d|f«      D ]+  }|j                  |d   g «      j                  d|d   i«       Œ- |S )uo   family code â†’ one entry per attached child, which carries ONLY the class (the schools may not give us names).z~SELECT s.family_code, s.class_name FROM students s JOIN families f ON f.code = s.family_code WHERE f.school_id=? ORDER BY s.idÚfamily_coder,   )r.   Ú
setdefaultÚappend)r3   r²   ÚoutÚrs       r4   Úfamily_students_mapr¹   ä   s^   € à!#€CØ�\‰\ð	VØX[ÐW]ó_ò Uˆð 	�‰�q˜Ñ'¨Ó,×3Ñ3°\À1À\Á?Ð4SÕTðUð €JrF   Úfam_studentsc                 óŒ   — | d   r| d   gS t        |j                  | d   g «      D �ch c]  }|d   ’Œ	 c}t        ¬«      S c c}w )z]The class the parent chose at checkout; otherwise the classes the family code is attached to.r,   r´   ©Úkey)Úsortedrˆ   r±   )Úrowrº   r‹   s      r4   Úorder_classesrÀ   î   sK   € à
ˆ<ÒØ�LÑ!Ð"Ð"Ü¨L×,<Ñ,<¸SÀÑ=OÐQSÓ,TÖU q�1�\“?ÒUÔ[iÔjÐjùÒUs   ¨Ac                 óT   — t        d„ | j                  d|f«      D «       t        ¬«      S )Nc              3   ó&   K  — | ]	  }|d    –— Œ y­w)r§   NrE   ©r‘   r¸   s     r4   r“   z%school_class_names.<locals>.<genexpr>ö   s   è ø€ Òp �1�V•9Ñpùó   ‚z1SELECT name FROM school_classes WHERE school_id=?r¼   )r¾   r.   r±   )r3   r²   s     r4   Úschool_class_namesrÅ   õ   s*   € ÜÑp d§l¡lÐ3fÐilÐhnÓ&oÔpÜ$ô&ð &rF   r^   Úorder_byc                 óX   — | j                  dt        › d|› �|f«      j                  «       S )Nú+SELECT * FROM orders WHERE school_id=? AND ú )r.   ÚPAID_SQLÚfetchall)r3   r²   rÆ   s      r4   Úpaid_ordersrÌ   ú   s2   € Ø�<‰<Ø
5´h°Z¸qÀÀ
ÐKÈcÈVóUßU]ÑU]ÓU_ð`rF   Úitemsc                 óæ   — dx}x}}d}d}| D ][  }|d   |d   z  }|d   }|j                  d«      r||z  }||d   z  }|dv sŒ6||d   z  }Œ?|dk(  r||z  }ŒJ||z  }|d	v sŒT||d   z  }Œ] |||||d
œS )u[   RÃ©partit le montant d'une commande : papier / magique IA / numÃ©rique. Compte les tirages.ç        r   Ú
unit_priceÚquantityÚ	format_idÚis_magic)zmagic-printz
magic-packÚdigital)zprint-10x15zprint-20x30Úpack)ÚpaperÚmagicrÔ   ÚprintsÚai©rˆ   )	rÍ   rÖ   r×   rÔ   rØ   rÙ   ÚitÚamountÚfids	            r4   Úitem_bucketsrÞ   ÿ   sÆ   € à!Ð!€EÐ!ˆE�GØ€FØ	
€BØò )ˆØ�LÑ! B z¡NÑ2ˆØ�‰oˆØ�6‰6�*ÔØ�V‰OˆEØ�"�Z‘.Ñ ˆBØÐ3Ò3Ø˜"˜Z™.Ñ(‘Ø�IÒØ�vÑ‰Gà�V‰OˆEØÐ<Ò<Ø˜"˜Z™.Ñ(‘ð)ð  U°wÈ&ÐXZÑ[Ð[rF   c                 óØ  — t        j                  | d   «      }t        | |«      }t        |«      }|d   }t	        d„ |D «       «      }g }|r/|d   r	d|d   › d�nd}|j                  |› d|d	kD  rd
nd› |› �«       |r|j                  d«       | d   | d   d d j                  «       | d   ||dj                  |«      xs t        |«      › d�| d   | d   | d   | d   | d   | d   ||d   dœS )NÚ
items_jsonrØ   c              3   óF   K  — | ]  }d |d   v s	|d   dk(  sŒ|d   –— Œ y­w)rÔ   rÒ   rÕ   rÑ   NrE   ©r‘   r¯   s     r4   r“   z%order_row_to_admin.<locals>.<genexpr>  s.   è ø€ Òl a°IÀÀ;ÁÑ4OÐSTÐU`ÑSaÐekÓSk�A�j•MÑlùs   ‚!—
!rÙ   z (dont z IA)r^   z tirager©   r‹   u
   numÃ©riqueÚidr(   r´   z + z article(s)ÚtotalÚstatusÚpayment_statusÚbatch_statusÚcustomer_emailÚ
created_at)rã   Úshort_idr´   ÚclassesrÍ   Úitems_summaryrä   rå   ræ   rç   rè   ré   rØ   rÙ   )	ÚjsonÚloadsrÀ   rÞ   Úsumr¶   rª   r•   r­   )	r¿   rº   rÍ   rë   ÚbÚn_printsÚ	n_digitalÚsummary_partsÚai_parts	            r4   Úorder_row_to_adminrõ     s(  € Ü�J‰J�s˜<Ñ(Ó)€EÜ˜C Ó.€GÜ�UÓ€AØ�‰{€HÜÑl¨5ÔlÓl€IØ€MÙØ-.¨tªW�G˜A˜d™G˜9 DÑ)¸"ˆØ×Ñ ˜z¨¸À1º±È"Ð0MÈgÈYÐWÔXÙØ×Ñ˜\Ô*à˜T™Ø˜T™ 2 A˜×,Ñ,Ó.Ø˜]Ñ+Ø ØØŸ™ MÓ2ÒP¼¸U»¸ÀKÐ6PØ˜W™Ø˜X™ØÐ.Ñ/Ø˜^Ñ,ØÐ.Ñ/Ø˜\Ñ*Ø!Ø˜4™ñð rF   Úcodesc                 ó,   ‡ — t        ˆ fd„|D «       «      S )Nc              3   óJ   •K  — | ]  }t        j                  ‰|«      –— Œ y ­w©N)r"   Úcount_family_photos)r‘   r’   rf   s     €r4   r“   z _count_photos.<locals>.<genexpr>4  s   øè ø€ ÒNÀ1Œ}×0Ñ0°¸A×>ÑNùs   ƒ #)rï   )rf   rö   s   ` r4   Ú_count_photosrû   3  s   ø€ ÜÓNÈÔNÓNÐNrF   c                   ót   — e Zd ZU  edd¬«      Zeed<    edd¬«      Zee   ed<    edd	¬«      Z	ee   ed
<   y)ÚSchoolCreater©   éx   ©Ú
min_lengthr:   r§   Né(   r=   rt   r8   Údirector_password)
r@   rA   rB   r   r§   rC   rD   rt   r   r  rE   rF   r4   rý   rý   9  s>   … Ù ¨sÔ3€Dˆ#Ó3Ù¨¸Ô<€Dˆ(�3‰-Ó<Ù',°TÀcÔ'JÐ�x ‘}ÔJrF   rý   c                   ój   — e Zd ZU  edd¬«      Zee   ed<   dZee	   ed<    edd¬«      Z
ee   ed<   y)ÚSchoolPatchNrþ   r=   r§   rc   r8   r  )r@   rA   rB   r   r§   r   rC   rD   rc   Úboolr  rE   rF   r4   r  r  ?  s;   … Ù¨¸Ô=€Dˆ(�3‰-Ó=Ø!€FˆH�T‰NÓ!Ù',°TÀcÔ'JÐ�x ‘}ÔJrF   r  z/api/admin/schoolsc                 óà  — t        | |dd¬«       g }t        «       5 }t        j                  «       D ]   }|d   }|j	                  d|f«      j                  «       d   }|j	                  d|f«      j                  «       d   }|j	                  dt        › �|f«      j                  «       }|j                  i |¥|||d   t        |d	   «      d
œ¥«       Œ¢ 	 d d d «       d|iS # 1 sw Y   d|iS xY w)Nr_   Fry   rã   ú4SELECT COUNT(*) AS n FROM families WHERE school_id=?Únú`SELECT COUNT(*) AS n FROM students s JOIN families f ON f.code=s.family_code WHERE f.school_id=?zVSELECT COUNT(*) AS n, COALESCE(SUM(total),0) AS rev FROM orders WHERE school_id=? AND Úrev)ÚfamiliesÚstudentsr+   ÚrevenueÚschools)	rŠ   r   r%   Úlist_schoolsr.   ÚfetchonerÊ   r¶   Úround)	r{   r|   r·   r3   r‹   r²   ÚfamÚstuÚos	            r4   r  r  E  s  € äˆM˜8 Z¸UÕCØ
€CÜ	‹ð n�TÜ×,Ñ,Ó.ò 	nˆAØ�D‘'ˆCØ—,‘,ÐUÐX[ÐW]Ó^×gÑgÓiÐjmÑnˆCØ—,‘,ð  5Ø7:°fó>ß>F¹h»jÈñNˆCà—‘ð  6Ü6>°ZðAØCFÀ&óJßJRÉ(Ë*ð à�J‰JÐl˜!Ðl¨¸#ÈÈ3ÉÔ\aÐbcÐdiÑbjÓ\kÒlÕmñ	n÷nð �sÐÐ÷nð �sÐÐús   œB8C!Ã!C-c                 ó^  — t        ||dd¬«       	 t        j                  | j                  | j                  | j
                  xs d «      }t        j                  ddt        |«      d|d   |d   d	|d	   i¬
«       d|iS # t        j                  $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   Fry   rƒ   rj   zschool.creater?   rã   r§   )r`   r]   Úresource_typeÚresource_idrf   rh   )rŠ   r%   Úcreate_schoolr§   rt   r  ÚSettingsErrorr
   rC   r   rp   r   )rU   rV   r{   r|   r?   Úes         r4   r  r  U  s¯   € ô ˆM˜8 Z¸UÕCð0Ü×-Ñ-¨d¯i©i¸¿¹ÀD×DZÑDZÒDbÐ^bÓcˆô ×Ñ˜O°ZÌIÐV]ÓL^Ø*2ÀÀtÁÐX^Ð_cÑXdØ%+¨V°F©^Ð$<õ>ð �fÐÐøô ×'Ñ'ò 0Ü˜C¬¨A«Ô/Ð/ûð0ús   ‘9A> Á>B,ÂB'Â'B,z/api/admin/schools/{school_id}c                 óŽ  — t        ||dd¬«       	 t        j                  | |j                  |j                  |j
                  ¬«      }t        j                  ddt        |«      d| | d	|j                  |j                  |j
                  d ud
œ¬«       d|iS # t        j                  $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   Fry   )r§   rc   r  rƒ   rj   zschool.updater?   rg   )r§   rc   Údirector_password_changed©r`   r]   r  r  rf   ra   rh   )rŠ   r%   Úupdate_schoolr§   rc   r  r  r
   rC   r   rp   r   )rf   rU   rV   r{   r|   r?   r  s          r4   Úpatch_schoolr  c  sÀ   € ô ˆM˜8 Z¸UÕCð0Ü×-Ñ-¨i¸d¿i¹iÐPT×P[ÑP[Ø@D×@VÑ@VôXˆô ×ÑØ ¼	À'Ó8JÐZbØ¨¸YØŸ™¨d¯k©kØ.2×.DÑ.DÈDÐ.PñRõSð
 �fÐÐøô ×'Ñ'ò 0Ü˜C¬¨A«Ô/Ð/ûð0ús   ‘7B ÂCÂ)B?Â?Cc                   ó:   — e Zd ZU eeeez  ez  ez  dz  f   ed<   y)ÚSettingsUpdateNÚsettings)	r@   rA   rB   ÚdictrC   ÚintÚfloatr  rD   rE   rF   r4   r!  r!  v  s%   … Ø�3˜˜c™	 EÑ)¨DÑ0°4Ñ7Ð7Ñ8Ô8rF   r!  z/api/admin/settingsc           
      óV  — t        | |dd«      }t        j                  j                  «       D ��cg c]-  \  }}||d   |j	                  d«      |j	                  d«      dœ‘Œ/ }}}t        j
                  |j                  «      t        j                  |j                  «      |dœS c c}}w )Nr_   re   ÚtypeÚminÚmax)r½   r'  r(  r)  )r"  r?   Úfields)rŠ   r%   ÚSETTINGS_SPECrÍ   rˆ   Úget_settingsrf   r‰   )r{   r|   ÚctxÚkÚvr*  s         r4   Úadmin_settingsr0  z  sš   € ä
�- ¨:°zÓ
B€Cä(×6Ñ6×<Ñ<Ó>÷@Ù�!�Qð   6¡°1·5±5¸³<ÈÏÉÈeËÓUð @€Fñ @ä&×3Ñ3°C·M±MÓBÜ$×/Ñ/°·±Ó>È&ñRð Rùó@s   °2B%c           
      ón  — t        ||d«      }t        j                  |j                  «      }	 t        j                  |j                  | j
                  «      }| j
                  D �ci c]J  }|j                  |«      |j                  |«      k7  sŒ'||j                  |«      |j                  |«      dœ“ŒL }	}|	rDt        j                  d|j                  t        |«      d|j                  |j                  |	d¬«       d	|iS # t        j                  $ r}t        dt        |«      ¬«      ‚d }~ww xY wc c}w )
Nr_   rƒ   rj   ©ÚfromÚtozsettings.updater?   rg   ©r`   r]   r  r  rf   rh   ra   r"  )rŠ   r%   r,  rf   Úupdate_settingsr"  r  r
   rC   rˆ   r   rp   rG   r   )
rU   rV   r{   r|   r-  ÚbeforeÚafterr  r.  Úchangeds
             r4   Úadmin_update_settingsr:  ƒ  s  € ô �- ¨:Ó
6€CÜ×(Ñ(¨¯©Ó7€Fð0Ü×.Ñ.¨s¯}©}¸d¿m¹mÓLˆð HLÇ}Á}ÖvÀ!ÐX^×XbÑXbÐcdÓXeÐin×irÑirÐstÓiuÓXuˆq˜6Ÿ:™: a›=°·	±	¸!³Ñ=Ñ=Ðv€GÐvÙÜ×ÑÐ 1¸3¿8¹8ÔPYÐZaÓPbØ.6ÀCÇMÁMÐ]`×]jÑ]jØ(/¸)õ	Eð ˜ÐÐøô ×'Ñ'ò 0Ü˜C¬¨A«Ô/Ð/ûð0üâvs#   ®*D Á&(D2Â&D2ÄD/ÄD*Ä*D/z/api/admin/statsc                 óè  — t        | |d«      }|j                  }t        «       5 }t        j                  ||«      }t        ||«      }|j                  d|f«      D �cg c]  }|d   ‘Œ	 }}t        |«      }	|j                  d|f«      j                  «       d   }
|j                  d|f«      j                  «       d   }d d d «       t        d„ D «       «      }dx}}t        «       }d}t        «       t        d	¬
«      z
  j                  «       }|D ]U  }t        t        j                   |d   «      «      }||d   z  }||d   z  }|j#                  |d   «       |d   |k\  sŒQ|d	z  }ŒW j%                  dd«      }d }	 t'        j(                  |«      t        «       j'                  «       z
  j*                  }t/        |j%                  d«      xs d«      }i d|“d|j%                  d«      “dt        |«      “d“d|“d|“d	rt1        dt        |«      z  |	z  «      nd“d|“d|“d|“d|“d|	“d
“d t3        |«      “d|“d!t1        ||z  dz  «      “d"|“S c c}w # 1 sw Y   �Œ�xY w# t,        $ r Y Œ½w xY w)#Nr_   ú+SELECT code FROM families WHERE school_id=?Úcoder	  r  zqSELECT COUNT(*) AS n FROM orders WHERE school_id=? AND payment_status IN ('unpaid','failed') AND status='pending'c              3   ó&   K  — | ]	  }|d    –— Œ y­w)rä   NrE   rÃ   s     r4   r“   zadmin_stats.<locals>.<genexpr>¤  s   è ø€ Ò+ �!�G•*Ñ+ùrÄ   r   r©   rI   rà   rØ   rÙ   r´   ré   Úclosure_dater^   Úrevenue_share_pctrf   Úschool_nameÚorders_countÚpending_unpaidÚnew_last_hourr  Ú
buyer_rateéd   Úprints_totalÚai_totalÚdays_to_closureÚfamilies_totalÚstudents_totalÚphotos_receivedÚrevenue_sharer"  )rŠ   rf   r   r%   r,  rÌ   r.   r­   r  rï   Úsetr   r   Ú	isoformatrÞ   rí   rî   Úaddrˆ   r   ÚfromisoformatÚdaysÚ	Exceptionr%  r  rû   )r{   r|   r-  r²   r3   r"  Úrowsr¸   rö   Ú
n_familiesÚ
n_studentsÚunpaidr  rØ   rÙ   ÚbuyersÚnew_hourÚhour_agorð   ÚclosureÚ	days_leftÚ	share_pcts                         r4   Úadmin_statsr^  –  sþ  € ä
�- ¨:Ó
6€CØ
�-‰-€CÜ	‹ð N�TÜ!×.Ñ.¨s°DÓ9ˆÜ˜4 Ó%ˆØ$(§L¡LÐ1^ÐadÐ`fÓ$gÖh˜q��6“ÐhˆÐhÜ˜“Zˆ
Ø—\‘\ð #8Ø:=¸óAßAIÁÃÈCñQˆ
à—‘ð 5Ø7:°fó>ß>F¹h»jÈñNˆ÷Nô Ñ+ dÔ+Ó+€GØ€O€FˆRÜ‹U€FØ€HÜ“œ9¨1Ô-Ñ-×8Ñ8Ó:€HØò ˆÜœŸ™ A l¡OÓ4Ó5ˆØ�!�H‘+ÑˆØ
ˆa�‰g‰ˆØ�
‰
�1�]Ñ#Ô$Øˆ\‰?˜hÓ&Ø˜‰M‰Hðð �l‰l˜>¨2Ó.€GØ€IðÜ×'Ñ'¨Ó0´6³8·=±=³?ÑB×HÑHˆ	ô �h—l‘lÐ#6Ó7Ò<¸1Ó=€IðØ˜3ðà˜8Ÿ<™<¨Ó6ðð 	œ3˜t›9ðð 	˜6ð	ð
 	˜8ðð 	˜7ðð 	ÁJœ5 ¤s¨6£{Ñ!2°ZÑ!?Ô@ÐTUðð 	˜6ðð 	˜2ðð 	˜7ðð 	˜9ðð 	˜:ðð 	˜:ðð 	œ=¨¨eÓ4ðð 	˜Yðð  	œ5 ¨9Ñ!4°sÑ!:Ó;ð!ð" 	˜8ð#ð ùò7 i÷Nñ Nûô6 ò Ùðús0   ¤8IÁIÁ(AIÅ;8I% ÉIÉI"É%	I1É0I1z/api/admin/ordersÚpaidz^(paid|all)$)Úpatternr,   Úpaymentc                 óÎ  — t        ||d«      }t        «       5 }t        ||j                  «      }|dk(  r4|j	                  dt
        › d�|j                  f«      j                  «       }n+|j	                  d|j                  f«      j                  «       }|D �cg c]  }t        ||«      ‘Œ }	}d d d «       | r	D �
cg c]  }
| |
d   v sŒ|
‘Œ }	}
d	iS c c}w # 1 sw Y   Œ+xY wc c}
w )Nr_   r_  rÈ   z ORDER BY created_at DESCz?SELECT * FROM orders WHERE school_id=? ORDER BY created_at DESCrë   r+   )rŠ   r   r¹   rf   r.   rÊ   rË   rõ   )r,   ra  r{   r|   r-  r3   rº   rT  r¸   r+   r  s              r4   Úadmin_ordersrc  Ï  sô   € ô �- ¨:Ó
6€CÜ	‹ð E�TÜ*¨4°·±Ó?ˆØ�fÒØ—<‘<Ð"MÌhÈZÐWpÐ qØ!$§¡Ð 0ó2ß2:±(³*ñ ð —<‘<Ð aØ!$§¡Ð 0ó2ß2:±(³*ð à?CÖD¸!Ô$ Q¨Õ5ÐDˆÐD÷Eñ Ø#ÖB˜ z°Q°y±\Ò'A’!ÐBˆÐBØ�fÐÐùò E÷Eð Eüò Cs*   ˜A?CÂCÂ*CÂ:C"ÃC"ÃCÃCc                   ó   — e Zd ZU eed<   y)ÚBatchStatusRequestrç   N)r@   rA   rB   rC   rD   rE   rF   r4   re  re  ä  s   … ØÔrF   re  z/api/admin/orders/{order_id}Úorder_idc                 óØ  — t        ||d«      }h d£}|j                  |vrt        ddt        |«      › �¬«      ‚t	        «       5 }|j                  d| |j                  f«      j                  «       }|st        dd¬«      ‚|j                  d	|j                  | f«       |j                  d
k(  r|j                  d| f«       n"|j                  dk(  r|j                  d| f«       |j                  «        d d d «       t        j                  d|j                  t        |«      d| |j                  d|j                  i|j                  dk(  rdnd¬«       d|j                  dœS # 1 sw Y   ŒoxY w)Nr_   >   ÚlabeledÚprintedÚto_printÚ	cancelledÚ	deliveredrƒ   zStatut invalide. Valeurs : rj   z@SELECT id, payment_status FROM orders WHERE id=? AND school_id=?r‚   zCommande introuvable.z+UPDATE orders SET batch_status=? WHERE id=?rl  z/UPDATE orders SET status='delivered' WHERE id=?rk  z/UPDATE orders SET status='cancelled' WHERE id=?zorder.batch_statusÚorderrç   rg   r2   r5  T)Úsuccessrç   )rŠ   rç   r
   r¾   r   r.   rf   r  r/   r   rp   rG   r   )	rf  rU   rV   r{   r|   r-  Úallowedr3   r¿   s	            r4   Úadmin_update_orderrp  è  sY  € ô �- ¨:Ó
6€CÚJ€GØ×Ñ Ñ'Ü˜CÐ*EÄfÈWÃoÐEVÐ(WÔXÐXÜ	‹ð 
�TØ�l‰lÐ]Ø$ c§m¡mÐ4ó6ß6>±h³jð 	áÜ Ð,CÔDÐDØ�‰ÐBÀT×EVÑEVÐX`ÐDaÔbØ×Ñ Ò+Ø�L‰LÐJÈXÈKÕXØ×Ñ +Ò-Ø�L‰LÐJÈXÈKÔXØ�‰Œ÷
ô ×ÑÐ0¸#¿(¹(ÌyÐY`ÓOaØ*1¸xÐSV×S`ÑS`Ø%3°T×5FÑ5FÐ$GØ26×2CÑ2CÀ{Ò2R¡YÐX^õ`ð ¨T×->Ñ->Ñ?Ð?÷
ð 
ús   ÁB/E Å E)c                 ó�  ‡— t        | |«      }t        | |«      }i Šddt        dt        fˆfd„}| j	                  d|f«      D ]  } ||d   |d   |d   «       Œ | j	                  d|f«      D ]  } ||d   «      }|d	   |d
   c|d<   |d<   Œ  |D ]¶  }t        t        j                  |d   «      «      }t        ||«      }|sŒ3|d   t        |«      z  }	|D ]G  }
‰j                  |
«      }|sŒ|dxx   |	z  cc<   |dxx   dz  cc<   |d   j                  |d   «       ŒI ‰j                  |d   «      }|sŒ§|dxx   |d   z  cc<   Œ¸ g }t        ‰j                  «       d„ ¬«      D ]\  }t        |j                  d«      «      }|d   rt        d|z  |d   z  «      nd|d<   t        |d   «      |d<   |j!                  |«       Œ^ |S )Nr§   r)   c                 óL   •— ‰j                  | || |ddddt        «       ddœ	«      S )Nr   rÏ   )	rã   r,   Ústudent_countr  r  r+   ÚsalesÚbuyer_familiesrØ   )rµ   rN  )r§   Úclass_idÚexpectedÚclss      €r4   Úentryzclasses_summary.<locals>.entry	  s3   ø€ Ø�~‰~˜dØ¨$ÀÐWXÐfgØ #¼»È!ñ%
ó ð 	rF   zDSELECT id, name, student_count FROM school_classes WHERE school_id=?rã   rs  z­SELECT s.class_name, COUNT(*) AS n, COUNT(DISTINCT s.family_code) AS f FROM students s JOIN families fa ON fa.code = s.family_code WHERE fa.school_id=? GROUP BY s.class_namer,   r  Úfr  r  rà   rä   rt  r+   r©   ru  r´   r   rØ   c                 ó   — t        | d   «      S ©Nr,   ©r±   ©Úxs    r4   ú<lambda>z!classes_summary.<locals>.<lambda>+  s   € ´¸qÀ¹Ó0O€ rF   r¼   rF  Úparticipation)NN)r¹   rÌ   rC   r#  r.   rÞ   rí   rî   rÀ   r­   rˆ   rP  r¾   ÚvaluesÚpopr  r¶   )r3   r²   rº   rT  ry  r¸   r’   rð   rë   Úsharer§   Úfirstr·   Ún_buyersrx  s                 @r4   Úclasses_summaryr‡    s  ø€ Ü& t¨SÓ1€LÜ�t˜SÓ!€Dà€Cñ”Cð ¼$õ ð �\‰\Ð`ÐcfÐbhÓiò 6ˆÙˆa�‰i˜˜4™ ! OÑ"4Õ5ð6à�\‰\ð	aØcfÐbhójò 6ˆñ �!�L‘/Ó"ˆØ'(¨¡v¨q°©vÐ$ˆˆ*‰�q˜’}ð	6ð ò +ˆÜœŸ™ A l¡OÓ4Ó5ˆô    <Ó0ˆÙØØ�'‘
œS ›\Ñ)ˆØò 	6ˆDØ—‘˜“ˆAÙØØˆg‹J˜%Ñ‹JØˆh‹K˜1Ñ‹KØÐÑ×#Ñ# A mÑ$4Õ5ð	6ð —‘˜ ™
Ó#ˆÚØ�(‹O˜q ™{Ñ*ŒOð#+ð& €CÜ�C—J‘J“LÑ&OÔPò ˆÜ�q—u‘uÐ-Ó.Ó/ˆØFGÈ
ÂmœU 3¨¡>°A°j±MÑ#AÔBÐYZˆˆ/ÑÜ˜1˜W™:Ó&ˆˆ'‰
Ø�
‰
�1�ð	ð
 €JrF   z/api/admin/classesc                 óŽ   — t        | |dd«      }t        «       5 }dt        ||j                  «      icd d d «       S # 1 sw Y   y xY w)Nr_   re   rë   )rŠ   r   r‡  rf   ©r{   r|   r-  r3   s       r4   Úadmin_classesrŠ  3  sE   € ä
�- ¨:°zÓ
B€CÜ	‹ð A�TØœ?¨4°·±Ó?Ð@÷A÷ Aò Aús	   ™;»Ac                   óR   — e Zd ZU  edd¬«      Zeed<    eddd¬«      Zee	   ed	<   y)
ÚClassInr©   r  rÿ   r§   Nr   éˆ  ©r>   ÚgeÚlers  )
r@   rA   rB   r   r§   rC   rD   rs  r   r$  rE   rF   r4   rŒ  rŒ  :  s*   … Ù ¨rÔ2€Dˆ#Ó2Ù#(°¸!ÀÔ#E€M�8˜C‘=ÔErF   rŒ  c                   óZ   — e Zd ZU  eddd¬«      Zee   ed<    eddd¬«      Zee	   ed	<   y)
Ú
ClassPatchNr©   r  ©r>   r   r:   r§   r   r�  rŽ  rs  )
r@   rA   rB   r   r§   r   rC   rD   rs  r$  rE   rF   r4   r’  r’  ?  s0   … Ù¨¸ÀrÔJ€Dˆ(�3‰-ÓJÙ#(°¸!ÀÔ#E€M�8˜C‘=ÔErF   r’  c                 óL   — t        j                  dd| «      j                  «       S )Nz\s+rÉ   )ÚreÚsubrm   )r§   s    r4   Ú_clean_classr—  D  s   € Ü�6‰6�&˜#˜tÓ$×*Ñ*Ó,Ð,rF   c           	      óJ  — t        ||d«      }t        | j                  «      }|st        dd¬«      ‚t	        «       5 }|j                  d|j                  |f«      j                  «       rt        dd¬«      ‚|j                  d|j                  || j                  f«      }|j                  «        |j                  }d d d «       t        j                  d	|j                  t        |«      |j                  || j                  d
œ¬«       d|| j                  dœiS # 1 sw Y   Œ_xY w)Nr_   rƒ   úClass name required.rj   ú9SELECT 1 FROM school_classes WHERE school_id=? AND name=?é™  úThis class already exists.zJINSERT INTO school_classes (school_id, name, student_count) VALUES (?,?,?)zclass.create)r,   rs  ©r`   r]   rf   rh   Úclass)rã   r,   rs  )rŠ   r—  r§   r
   r   r.   rf   r  rs  r/   Ú	lastrowidr   rp   rG   r   )	rU   rV   r{   r|   r-  r§   r3   ÚcurÚnew_ids	            r4   Úcreate_classr¢  H  s  € ô �- ¨:Ó
6€CÜ˜Ÿ	™	Ó"€DÙÜ˜CÐ(>Ô?Ð?Ü	‹ð �TØ�<‰<ÐSÐVY×VcÑVcÐeiÐUjÓk×tÑtÔvÜ Ð,HÔIÐIØ�l‰lÐgØŸM™M¨4°×1CÑ1CÐDóFˆà�‰ŒØ—‘ˆ÷ô ×Ñ˜N°C·H±HÌÐSZÓI[Ðgj×gtÑgtØ37È$×J\ÑJ\Ñ$]õ_à˜F°$È×I[ÑI[Ñ\Ð]Ð]÷ð ús   ¼A?DÄD"z/api/admin/classes/{class_id}rv  c           	      óf  — t        ||d«      }|j                  }t        «       5 }|j                  d| |f«      j	                  «       }|st        dd¬«      ‚|d   }	|j                  �Ât        |j                  «      }
|
st        dd¬«      ‚|
|	k7  r™|j                  d	||
f«      j	                  «       rt        d
d¬«      ‚|j                  d|
| f«       |j                  d|f«      D �cg c]  }|d   ‘Œ	 }}|D ]  }|j                  d|
||	f«       Œ |j                  d|
|	|f«       |
}	d|j                  v r|j                  d|j                  | f«       |j                  «        d d d «       t        j                  d|j                  t        |«      || 	dœ¬«       d|	dœS c c}w # 1 sw Y   ŒCxY w)Nr_   ú7SELECT * FROM school_classes WHERE id=? AND school_id=?r‚   úClass not found.rj   r§   rƒ   r™  rš  r›  rœ  z+UPDATE school_classes SET name=? WHERE id=?r<  r=  zEUPDATE students SET class_name=? WHERE family_code=? AND class_name=?zAUPDATE orders SET class_name=? WHERE class_name=? AND school_id=?rs  z4UPDATE school_classes SET student_count=? WHERE id=?zclass.update)rã   r,   r�  T)rn  r,   )rŠ   rf   r   r.   r  r
   r§   r—  Úmodel_fields_setrs  r/   r   rp   rG   r   )rv  rU   rV   r{   r|   r-  r²   r3   r   r§   Únew_namer¸   rö   r=  s                 r4   Úupdate_classr¨  [  sÄ  € ô �- ¨:Ó
6€CØ
�-‰-€CÜ	‹ð �TØ�l‰lÐTÐW_ÐadÐVeÓf×oÑoÓqˆÙÜ Ð,>Ô?Ð?Ø�6‰{ˆØ�9‰9Ð Ü# D§I¡IÓ.ˆHÙÜ# CÐ0FÔGÐGØ˜4ÒØ—<‘<Ð [Ð^aÐckÐ]lÓm×vÑvÔxÜ'¨Ð4PÔQÐQØ—‘ÐJÈXÐW_ÐL`Ôaà,0¯L©LÐ9fÐilÐhnÓ,oÖp q˜˜6›Ðp�ÐpØ!ò 9�DØ—L‘LÐ!hØ"*¨D°$Ð!7õ9ð9ð —‘Ð`ÐckÐmqÐsvÐbwÔxØ�Ø˜d×3Ñ3Ñ3Ø�L‰LÐOÐRV×RdÑRdÐfnÐQoÔpØ�‰Œ÷-ô. ×Ñ˜N°C·H±HÌÐSZÓI[ÐgjØ+3À4Ñ$HõJà¨4Ñ0Ð0ùò q÷ð ús   ¤CF'Ã(F"Ã4A1F'Æ"F'Æ'F0c           	      óü  — t        ||d«      }|j                  }t        «       5 }|j                  d| |f«      j	                  «       }|st        dd¬«      ‚|j                  d||d   f«      j	                  «       d   }|rt        d	|› d
�¬«      ‚|j                  d| f«       |j                  «        d d d «       t        j                  d|j                  t        |«      |dd   id¬«       ddiS # 1 sw Y   Œ@xY w)Nr_   r¤  r‚   r¥  rj   zsSELECT COUNT(*) AS n FROM students s JOIN families f ON f.code=s.family_code WHERE f.school_id=? AND s.class_name=?r§   r  r›  u?    family code(s) are attached to this class â€” move them first.z%DELETE FROM school_classes WHERE id=?zclass.deleter,   rg   ©r`   r]   rf   rh   ra   rn  T)rŠ   rf   r   r.   r  r
   r/   r   rp   rG   r   )	rv  rV   r{   r|   r-  r²   r3   r   Úattacheds	            r4   Údelete_classr¬  |  s
  € ô �- ¨:Ó
6€CØ
�-‰-€CÜ	‹ð 
�TØ�l‰lÐTÐW_ÐadÐVeÓf×oÑoÓqˆÙÜ Ð,>Ô?Ð?Ø—<‘<ð5Ø7:¸CÀ¹KÐ6HóJçJRÉ(Ë*ÐUXñZˆñ Ü ¨x¨jÐ8wÐ,xÔyÐyØ�‰Ð<¸x¸kÔJØ�‰Œ÷
ô ×Ñ˜N°C·H±HÌÐSZÓI[ÐgjØ%1°3°v±;Ð$?È)õUà�tÐÐ÷
ð 
ús   ¤BC2Ã2C;z/api/admin/families/{code}r=  c                 óö  — t        ||d«      }|j                  }| j                  «       j                  «       } t	        «       5 }|j                  d| |f«      j                  «       st        dd¬«      ‚|j                  dt        › �| f«      j                  «       d   }|rt        dd	|› d
�¬«      ‚|j                  d| |f«      j                  }|j                  d| f«       |j                  d| f«       |j                  «        ddd«       t        j                  || «      }	t        j                  |«       t        j                  d|j                   t#        |«      |d| |	dœd¬«       d|	dœS # 1 sw Y   ŒlxY w)zÎRemove a family code (a wrong one, a test, or what blocks deleting a class). A code with PAID orders is
    never removed: it is the proof of a sale. The photos go to the restorable trash, not to /dev/null.r_   ú3SELECT 1 FROM families WHERE code=? AND school_id=?r‚   úFamily not found.rj   z9SELECT COUNT(*) AS n FROM orders WHERE family_code=? AND r  r›  zThis code has z% paid order(s) and cannot be deleted.z6DELETE FROM orders WHERE family_code=? AND school_id=?ú(DELETE FROM students WHERE family_code=?z!DELETE FROM families WHERE code=?Nzfamily.deleteÚfamily)Úunpaid_orders_removedÚfiles_to_trashÚcritical©r`   r]   rf   r  r  rh   ra   T)rn  Úphotos_moved_to_trash)rŠ   rf   rm   rª   r   r.   r  r
   rÊ   Úrowcountr/   r"   Údelete_family_photosÚinvalidate_families_cacher   rp   rG   r   )
r=  rV   r{   r|   r-  r²   r3   r_  ÚdroppedÚphotoss
             r4   Údelete_familyr¼  ‘  sj  € ô
 �- ¨:Ó
6€CØ
�-‰-€CØ�:‰:‹<×ÑÓ€DÜ	‹ð 	�TØ�|‰|ÐQÐTXÐZ]ÐS^Ó_×hÑhÔjÜ Ð,?Ô@Ð@Ø�|‰|ÐWÔX`ÐWaÐbÐeiÐdkÓl×uÑuÓwÐx{Ñ|ˆÙÜ ¨n¸T¸FÐBgÐ,hÔiÐiØ—,‘,ÐWÐZ^Ð`cÐYdÓe×nÑnˆØ�‰Ð?À$ÀÔIØ�‰Ð8¸4¸'ÔBØ�‰Œ÷	ô ×/Ñ/°°TÓ:€FÜ×+Ñ+¨CÔ0Ü×Ñ˜O°S·X±XÌ)ÐT[ÓJ\ÐhkØ*2ÀØ>EÐY_Ñ$`Ðkuõwð °fÑ=Ð=÷	ð 	ús   ÁCE/Å/E8z/api/admin/shootr8   r9   Úbasec           
      ó¬  — t        ||d«      }|j                  }ddlm} |j	                  |«      }t        t        j                  |«      | |«      }t        «       5 }	t        |	|«      }
d d d «       t        j                  |«      j                  d«      dk(  |r|› d|› �nd 
D �cg c]  }||j                  ||«      dœ‘Œ c}dœS # 1 sw Y   Œ^xY wc c}w )	Nr_   r   ©Úshoot_serviceÚ
shoot_modeÚtruez	/shoot?t=)r,   Úqr)ÚenabledÚlinkrë   )rŠ   rf   ÚservicesrÀ  Ú	get_tokenÚ_portal_baser%   r,  r   rÅ   rˆ   Úclass_qr_payload)rV   r½  r{   r|   r-  r²   rÀ  rb   Úportalr3   rë   r’   s               r4   Úadmin_shootrË  ­  sÒ   € ô �- ¨:Ó
6€CØ
�-‰-€CÝ&Ø×#Ñ# CÓ(€EÜœ.×5Ñ5°cÓ:¸GÀTÓJ€FÜ	‹ð 0�TÜ$ T¨3Ó/ˆ÷0ô "×.Ñ.¨sÓ3×7Ñ7¸ÓEÈÑOÙ/4�6�(˜) E 7Ñ+¸$Ø]dÖeÐXY 1¨M×,JÑ,JÈ3ÐPQÓ,RÓSÒeñð ÷0ð 0üò
 fs   ÁCÂ$CÃCz/api/admin/shoot/tokenc                 óÔ   — t        ||d«      }ddlm} |j                  |j                  «       t        j                  d|j                  t        | «      |j                  d¬«       ddiS )	za(Re)generate the phone link. The previous one stops working at once: use it when a phone is lost.r_   r   r¿  zshoot.token_rotatedrg   )r`   r]   rf   ra   rn  T)	rŠ   rÆ  rÀ  Úrotate_tokenrf   r   rp   rG   r   )rV   r{   r|   r-  rÀ  s        r4   Úadmin_shoot_tokenrÎ  ¾  s\   € ô �- ¨:Ó
6€CÝ&Ø×Ñ˜sŸ}™}Ô-Ü×ÑÐ1¸3¿8¹8ÔPYÐZaÓPbØ&)§m¡m¸iõIà�tÐÐrF   i  ` rY   Ú
product_idc                 óF   — t         j                  | z  t        |«      › d�z  S )Nz.webp)r   ÚPRODUCTS_DIRr$  )rf   rÏ  s     r4   Úproduct_image_pathrÒ  Ð  s$   € Ü×Ñ Ñ*´°J³Ð/@ÀÐ-FÑFÐFrF   c                 ó–   — | d   | d   | d   | d   | d   t        | d   «      t        | d   «      | d   rd	|› d
| d   › d| d   › �dœS d dœS )Nrã   Úname_frÚname_enÚname_esÚpriceÚphysicalrc   Ú	has_imagez/api/portal/product-image/ú/z.webp?v=)rã   rÔ  rÕ  rÖ  r×  rØ  rc   Ú	image_url)r  )r¿   rf   s     r4   Úproduct_to_dictrÜ  Ô  sƒ   € à�$‰i C¨	¡N¸sÀ9¹~ÐZ]Ð^gÑZhØ�W‘¬4°°J±Ó+@ÌDÐQTÐU]ÑQ^ÓL_à˜KÒ(ð 2°)°¸A¸cÀ$¹i¸[ÈÐQTÐU`ÑQaÐPbÐcñð ð /3ñ	ð rF   c                   ó”   — e Zd ZU  edd¬«      Zeed<    edd¬«      Zeed<    edd¬«      Zeed<    ed	d
¬«      Z	e
ed<   dZeed<   y)Ú	ProductInr©   éP   rÿ   rÔ  r^   r=   rÕ  rÖ  r   é'  ©r�  r�  r×  TrØ  N)r@   rA   rB   r   rÔ  rC   rD   rÕ  rÖ  r×  r%  rØ  r  rE   rF   r4   rÞ  rÞ  Ý  sP   … Ù A°"Ô5€GˆSÓ5Ù °Ô3€GˆSÓ3Ù °Ô3€GˆSÓ3Ù˜A %Ô(€Eˆ5Ó(Ø€HˆdÔrF   rÞ  c                   óÊ   — e Zd ZU  eddd¬«      Zee   ed<    edd¬«      Zee   ed<    edd¬«      Z	ee   ed<    edd	d
¬«      Z
ee   ed<   dZee   ed<   dZee   ed<   y)ÚProductPatchNr©   rß  r“  rÔ  r=   rÕ  rÖ  r   rà  rŽ  r×  rØ  rc   )r@   rA   rB   r   rÔ  r   rC   rD   rÕ  rÖ  r×  r%  rØ  r  rc   rE   rF   r4   rã  rã  å  sv   … Ù"¨4¸AÈ"ÔM€GˆX�c‰]ÓMÙ"¨4¸BÔ?€GˆX�c‰]Ó?Ù"¨4¸BÔ?€GˆX�c‰]Ó?Ù"¨4°A¸%Ô@€Eˆ8�E‰?Ó@Ø#€Hˆh�t‰nÓ#Ø!€FˆH�T‰NÔ!rF   rã  z/api/admin/productsc                 ó  — t        | |d«      }t        «       5 }|j                  d|j                  f«      j	                  «       }d d d «       dD �cg c]  }t        ||j                  «      ‘Œ c}iS # 1 sw Y   Œ/xY wc c}w )Nr_   zGSELECT * FROM school_products WHERE school_id=? ORDER BY sort_order, idÚproducts)rŠ   r   r.   rf   rË   rÜ  )r{   r|   r-  r3   rT  r¸   s         r4   Úlist_productsræ  î  sy   € ä
�- ¨:Ó
6€CÜ	‹ð 9�TØ�|‰|ÐeØ Ÿ]™]Ð,ó.ß.6©h«jð 	÷9ð ÀDÖI¸qœ¨¨C¯M©MÕ:ÒIÐJÐJ÷9ð 9üò Js   ˜,A2ÁA>Á2A;c                 ó˜  — t        ||d«      }|j                  }t        «       5 }|j                  d|| j                  j                  «       | j                  j                  «       | j                  j                  «       t        | j                  d«      t        | j                  «      t        «       f«      }|j                  «        |j                  d|j                  f«      j                  «       }d d d «       t!        j"                  d|j$                  t'        |«      |d   |d   |d   d	œ¬
«       dt)        ||«      iS # 1 sw Y   ŒQxY w)Nr_   zvINSERT INTO school_products (school_id, name_fr, name_en, name_es, price, physical, created_at) VALUES (?,?,?,?,?,?,?)é   ú(SELECT * FROM school_products WHERE id=?zproduct.createrã   rÔ  r×  )rã   r§   r×  r�  Úproduct)rŠ   rf   r   r.   rÔ  rm   rÕ  rÖ  r  r×  r$  rØ  r   r/   rŸ  r  r   rp   rG   r   rÜ  )	rU   rV   r{   r|   r-  r²   r3   r   r¿   s	            r4   Úcreate_productrë  ÷  s!  € ô �- ¨:Ó
6€CØ
�-‰-€CÜ	‹ð d�TØ�l‰lð%à�$—,‘,×$Ñ$Ó&¨¯©×(:Ñ(:Ó(<¸d¿l¹l×>PÑ>PÓ>RÔTYÐZ^×ZdÑZdÐfgÓThÜ�—‘Ó¤£ð/ó0ˆð
 	�‰ŒØ�l‰lÐEÈÏÉÐGWÓX×aÑaÓcˆ÷dô ×ÑÐ,°c·h±hÌ9ÐU\ÓK]ÐilØ+.¨t©9¸cÀ)¹nÐWZÐ[bÑWcÑ$dõfà” s¨CÓ0Ð1Ð1÷dð dús   ¤CE Å E	z /api/admin/products/{product_id}c                 óê  — t        ||d«      }|j                  }|j                  d¬«      j                  «       D ��ci c]  \  }}|€Œ	||“Œ }	}}d|	v rt	        |	d   d«      |	d<   dD ]  }
|
|	v sŒt        |	|
   «      |	|
<   Œ dD ]  }||	v sŒ|	|   j                  «       |	|<   Œ |	j                  d«      d	k(  rt        d
d¬«      ‚t        «       5 }|j                  d| |f«      j                  «       st        dd¬«      ‚|	rQdj                  d„ |	D «       «      }|j                  d|› d�g |	j                  «       ¢| ‘­«       |j                  «        |j                  d| f«      j                  «       }d d d «       t        j                   d|j"                  t%        |«      |d| i|	j                  «       D ��ci c]  \  }}||“Œ
 c}}¥¬«       dt'        |«      iS c c}}w # 1 sw Y   ŒpxY wc c}}w )Nr_   T)Úexclude_unsetr×  rè  )rØ  rc   )rÔ  rÕ  rÖ  rÔ  r^   rƒ   zProduct name required.rj   ú8SELECT 1 FROM school_products WHERE id=? AND school_id=?r‚   úProduct not found.ú, c              3   ó&   K  — | ]	  }|› d �–— Œ y­w)z=?NrE   )r‘   r.  s     r4   r“   z!update_product.<locals>.<genexpr>  s   è ø€ Ò6¨! ˜s "œXÑ6ùrÄ   zUPDATE school_products SET z WHERE id=?ré  zproduct.updaterã   r�  rê  )rŠ   rf   Ú
model_dumprÍ   r  r$  rm   rˆ   r
   r   r.   r  r•   r‚  r/   r   rp   rG   r   rÜ  )rÏ  rU   rV   r{   r|   r-  r²   r.  r/  r*  Úflagr3   Úsetsr¿   s                 r4   Úupdate_productrõ  	  sú  € ô �- ¨:Ó
6€CØ
�-‰-€CØ#Ÿ™¸T˜ÓB×HÑHÓJ×\‘t�q˜!ÈaÉmˆa�‰dÐ\€FÑ\Ø�&ÑÜ  w¡°Ó3ˆˆw‰Ø&ò -ˆØ�6Š>Ü˜v d™|Ó,ˆF�4ŠLð-ð /ò *ˆØ�Š;Ø˜q™	Ÿ™Ó)ˆF�1ŠIð*ð ‡z�z�)Ó Ò"Ü˜CÐ(@ÔAÐAÜ	‹ð a�TØ�|‰|ÐVÐYcÐehÐXiÓj×sÑsÔuÜ Ð,@ÔAÐAÙØ—9‘9Ñ6¨vÔ6Ó6ˆDØ�L‰LÐ6°t°f¸KÐHÐJhÈFÏMÉMËOÐJhÐ]gÑJhÔiØ�K‰KŒMØ�l‰lÐEÈ
À}ÓU×^Ñ^Ó`ˆ÷aô ×ÑÐ,°c·h±hÌ9ÐU\ÓK]ÐilØ%)¨:Ð$[È6Ï<É<Ë>×9ZÁ4À1Àa¸!¸Q¹$Ó9ZÐ$[õ]à” s¨CÓ0Ð1Ð1ùó+ ]÷að aüó :[s   ½
GÁGÃB$G#Æ8G/Ç#G,c           	      óâ  — t        ||d«      }|j                  }t        «       5 }|j                  d| |f«      j	                  «       }|st        dd¬«      ‚|j                  d| f«       |j                  «        d d d «       	 t        || «      j                  d¬«       t        j                  d	|j                  t        |«      || d
   dœd¬«       ddiS # 1 sw Y   Œ^xY w# t        $ r Y ŒOw xY w)Nr_   z8SELECT * FROM school_products WHERE id=? AND school_id=?r‚   rï  rj   z&DELETE FROM school_products WHERE id=?T)Ú
missing_okzproduct.deleterÔ  )rã   r§   rg   rª  rn  )rŠ   rf   r   r.   r  r
   r/   rÒ  ÚunlinkÚOSErrorr   rp   rG   r   )rÏ  rV   r{   r|   r-  r²   r3   r¿   s           r4   Údelete_productrú  &  sò   € ô �- ¨:Ó
6€CØ
�-‰-€CÜ	‹ð �TØ�l‰lÐUÐXbÐdgÐWhÓi×rÑrÓtˆÙÜ Ð,@ÔAÐAØ�‰Ð=À
¸}ÔMØ�‰Œ÷ðÜ˜3 
Ó+×2Ñ2¸dÐ2ÔCô ×ÑÐ,°c·h±hÌ9ÐU\ÓK]ÐilØ+5¸sÀ9¹~Ñ$NÐYbõdà�tÐÐ÷ð ûô ò Ùðús   ¤ACÂC" ÃCÃ"	C.Ã-C.z&/api/admin/products/{product_id}/image.Úfilec              ƒ   ór  ‡ ‡‡	‡
‡‡K  — t        j                  t        ||d«      ƒ d{  –—† Š‰j                  Šdt        fˆ ˆfd„}t        j                  |«      ƒ d{  –—† st        dd¬«      ‚|j                  t        dz   «      ƒ d{  –—† Št        ‰«      t        kD  rt        d	d
¬«      ‚dt        fˆfd„}	 t        j                  |«      ƒ d{  –—† Š	t        |«      Š
dt        fˆˆ	ˆ
ˆ ˆfd„}dt        j                  |«      ƒ d{  –—† iS 7 Œê7 Œ¶7 Œ‹7 ŒD# t        $ r t        dd¬«      ‚w xY w7 Œ'­w)z£Example visual of the product. Re-encoded to WebP: whatever the client sends, only an image we produced
    is ever served (no SVG/HTML smuggled through, no EXIF).r_   Nr)   c                  óš   •— t        «       5 } t        | j                  d‰‰f«      j                  «       «      cd d d «       S # 1 sw Y   y xY w)Nrî  )r   r  r.   r  )r3   rÏ  r²   s    €€r4   Ú_existsz%upload_product_image.<locals>._existsD  sI   ø€ Ü‹Xð 	D˜Ü˜Ÿ™Ð%_Ø&0°#Ð%6ó8ß8@¹»
óD÷	D÷ 	Dò 	Dús   Œ+AÁA
r‚   rï  rj   r©   i�  zImage too large (max 6 MB).c                  ó–  •— ddl m} m} | j                  t	        j
                  ‰«      «      5 }|j                  |«      }|j                  t        t        f«       t	        j
                  «       }|j                  d|j                  «       v rdnd«      j                  |dd¬«       |j                  «       cd d d «       S # 1 sw Y   y xY w)	Nr   )ÚImageÚImageOpsÚAÚRGBAÚRGBÚWEBPéT   )Úquality)ÚPILr   r  ÚopenÚioÚBytesIOÚexif_transposeÚ	thumbnailÚPRODUCT_IMAGE_PXÚconvertÚgetbandsÚsaveÚgetvalue)r   r  Úimr·   Úraws       €r4   r  z%upload_product_image.<locals>.convertN  s–   ø€ ß'Ø�Z‰ZœŸ
™
 3›Ó(ð 	"¨BØ×(Ñ(¨Ó,ˆBØ�L‰LÔ*Ô,<Ð=Ô>Ü—*‘*“,ˆCØ�J‰J ¨¯©«Ñ!5‘v¸5ÓA×FÑFÀsÈFÐ\^ÐFÔ_Ø—<‘<“>÷	"÷ 	"ò 	"ús   ®BB?Â?Crƒ   zNot a valid image.c            
      ó¶  •— t        ‰‰«      } | j                  j                  dd¬«       | j                  ‰«       t	        «       5 }|j                  d‰f«       |j                  «        |j                  d‰f«      j                  «       }d d d «       t        j                  d‰j                  ‰‰‰t        ‰«      dœ¬«       t        ‰«      S # 1 sw Y   ŒExY w)NT)ÚparentsÚexist_okz;UPDATE school_products SET has_image=has_image+1 WHERE id=?ré  zproduct.image)rã   Úbytesr�  )rÒ  ÚparentÚmkdirÚwrite_bytesr   r.   r/   r  r   rp   rG   r­   rÜ  )Úpathr3   r¿   r-  Údatarr   rÏ  r²   s      €€€€€r4   Ú_savez#upload_product_image.<locals>._save]  sÃ   ø€ Ü! # zÓ2ˆØ�‰×Ñ $°ÐÔ6Ø×Ñ˜ÔÜ‹Xð 	e˜Ø�L‰LÐVÐYcÐXeÔfØ�K‰KŒMØ—,‘,ÐIÈJÈ=ÓY×bÑbÓdˆC÷	eô 	×Ñ ¸¿¹ÈbÐ\_Ø/9ÄCÈÃIÑ(Nõ	Pä˜s CÓ(Ð(÷	eð 	eús   ÁACÃCrê  )ÚasyncioÚ	to_threadrŠ   rf   r  r
   ÚreadÚMAX_PRODUCT_IMAGE_BYTESr­   r  rS  r   r#  )rÏ  rV   rû  r{   r|   rþ  r  r  r-  r  rr   r  r²   s   `       @@@@@r4   Úupload_product_imager#  :  s$  ýè ø€ ô ×!Ñ!¤'¨=¸(ÀJÓO×
O€CØ
�-‰-€CðD”Tö Dô ×"Ñ" 7Ó+×+Ð+Ü˜CÐ(<Ô=Ð=Ø—	‘	Ô1°AÑ5Ó6×
6€CÜ
ˆ3ƒxÔ)Ò)Ü˜CÐ(EÔFÐFð"”Uõ "ð>Ü×&Ñ& wÓ/×/ˆô 
�7Ó	€Bð
)”4÷ 
)ñ 
)ð œW×.Ñ.¨uÓ5×5Ð6Ð6ðO Pøð ,øà
6øð 0ùÜò >Ü˜CÐ(<Ô=Ð=ð>úð 6úsi   ˆ D7¨D©5D7ÁDÁ,D7ÂDÂ/D7Â<D ÃDÃD Ã4D7ÄD5ÄD7ÄD7ÄD7ÄD ÄD2Ä2D7z7/api/portal/product-image/{school_id}/{product_id}.webpc                 óú   — t        «       5 }|j                  d|| f«      j                  «       }ddd«       t        | |«      }r|j	                  «       st        dd¬«      ‚t        |dddi¬	«      S # 1 sw Y   ŒDxY w)
zHPublic: a catalogue picture is marketing material, not a family's photo.zUSELECT 1 FROM school_products WHERE id=? AND school_id=? AND active=1 AND has_image>0Nr‚   zImage not found.rj   z
image/webpúCache-Controlúpublic, max-age=86400)Ú
media_typeÚheaders)r   r.   r  rÒ  Úis_filer
   r   )rf   rÏ  r3   r¿   r  s        r4   Úproduct_imager*  k  s   € ô 
‹ð ?�TØ�l‰lÐrØ&¨	Ð2ó4ß4<±H³Jð 	÷?ô ˜i¨Ó4€DÙ�d—l‘l”nÜ˜CÐ(:Ô;Ð;Ü˜¨ÀÐQhÐ?iÔjÐj÷?ð ?ús   ‹#A1Á1A:z/api/admin/familiesÚqc                 óú  — t        ||d«      }|j                  }t        «       5 }t        ||«      }|j	                  dt
        › �|f«      D �ch c]  }|d   ’Œ	 }}|j	                  d|f«      j                  «       }	d d d «       g }
	D ]»  }j                  |d   g «      D �cg c]  }|d   ‘Œ	 }}|d   |d   |t        |«      dkD  |d   v t        j                  ||d   «      |d	   xs d
dœ}| rB| j                  «       |d   › d|d   xs d
› ddj                  |«      › �j                  «       vrŒ«|
j                  |«       Œ½ |
j                  d„ ¬«       |
t        |
«      dœS c c}w # 1 sw Y   ŒñxY wc c}w )Nr_   z>SELECT DISTINCT family_code FROM orders WHERE school_id=? AND r´   z6SELECT * FROM families WHERE school_id=? ORDER BY coder=  r,   Úemailr©   ré   r^   )r=  r-  rë   Ú
is_siblingÚ
has_ordersÚcard_urlré   rÉ   c                 óD   — t        | d   r| d   d   nd«      | d   | d   fS )Nrë   r   ú~ré   r=  r}  ©rz  s    r4   r€  z admin_families.<locals>.<lambda>�  s1   € œN¸aÀ	ºl¨1¨Y©<¸ª?ÐPSÓTÐVWÐXdÑVeÐghÐioÑgpÐq€ rF   r¼   )r  rä   )rŠ   rf   r   r¹   r.   rÊ   rË   rˆ   r­   r!   r0  rn   r•   r¶   Úsort)r+  r{   r|   r-  r²   r3   rº   r¸   rX  rT  r·   r‹   rë   r  s                 r4   Úadmin_familiesr5  y  sª  € ô �- ¨:Ó
6€CØ
�-‰-€CÜ	‹ð i�TÜ*¨4°Ó5ˆØ,0¯L©LØLÌXÈJÐWÐZ]ÐY_ó-aö b q�!�MÓ"ð bˆð bà�|‰|ÐTÐWZÐV\Ó]×fÑfÓhˆ÷	ið
 €CØò 	ˆØ,8×,<Ñ,<¸Q¸v¹YÈÓ,KÖL q�1�\“?ÐLˆÐLà�f‘I¨¨'©
¸wÜ˜g›,¨Ñ*¸!¸F¹)ÀvÐ:MÜ%×.Ñ.¨s°A°f±IÓ>ÈaÐP\ÉoÒNcÐacñ
ˆñ
 �—‘“ S¨¡[ M°°3°w±<Ò3EÀ2Ð2FÀaÈÏÉÐQXÓHYÐGZÐ"[×"aÑ"aÓ"cÑcØØ�
‰
�3�ð	ð ‡H�HÑq€HÔrØ¤c¨#£hÑ/Ð/ùò!b÷ið iüò Ms#   ¤)E,ÁE'Á#E,Â"E8Å'E,Å,E5c                   óz   — e Zd ZU  edd¬«      Zee   ed<    edd¬«      Ze	e   ed<    edd	¬«      Z
e	e   ed
<   y)ÚFamilyCreater©   é   rÿ   rë   Nr8   r=   r-  é   r=  )r@   rA   rB   r   rë   ÚlistrC   rD   r-  r   r=  rE   rF   r4   r7  r7  –  sA   … Ù¨!¸Ô;€GˆT�#‰YÓ;Ù ¨¸#Ô>€Eˆ8�C‰=Ó>Ù¨¸Ô<€Dˆ(�3‰-Ô<rF   r7  c                   ó^   — e Zd ZU  eddd¬«      Zeee      ed<    edd¬«      Z	ee   ed<   y)	ÚFamilyPatchNr©   r8  r“  rë   r8   r=   r-  )
r@   rA   rB   r   rë   r   r:  rC   rD   r-  rE   rF   r4   r<  r<  œ  s4   … Ù#(°À!ÐPRÔ#S€GˆX�d˜3‘iÑ ÓSÙ ¨¸#Ô>€Eˆ8�C‰=Ô>rF   r<  z[^@\s]+@[^@\s]+\.[^@\s]+rë   c                 ó  — t        t        | |«      «      }|D �cg c]  }t        |«      ‘Œ }}t        |D �ch c]	  }||vsŒ|’Œ c}«      }|r|r't	        dddj                  |xs dg«      z   dz   ¬«      ‚|S c c}w c c}w )Nrƒ   zUnknown class: rð  z(empty)z&. Create it in the classes list first.rj   )rN  rÅ   r—  r¾   r
   r•   )r3   r²   rë   Úknownr’   ÚcleanedÚunknowns          r4   Ú_valid_classesrA  ¤  sŒ   € ÜÔ" 4¨Ó-Ó.€EØ(/Ö0 1Œ|˜A�Ð0€GÐ0Ü Ö;˜A¨A°UªN’aÒ;Ó<€GÙ‘gÜ˜CÐ(9¸D¿I¹IÀgÒF\ÐR[ÐQ\Ó<]Ñ(]ØFñ)Gô Hð 	Hà€Nùò 1ùÚ;s   šA9·	A>ÁA>c           	      óR  — | j                  d||f«      j                  «       }| j                  d|f«      D �cg c]  }|d   ‘Œ	 }}t        | j                  dt        › d�|f«      j                  «       «      }||d   |t	        |«      dkD  |t        j                  ||«      dœS c c}w )	Nz3SELECT * FROM families WHERE code=? AND school_id=?ú?SELECT class_name FROM students WHERE family_code=? ORDER BY idr,   z-SELECT 1 FROM orders WHERE family_code=? AND z LIMIT 1r-  r©   )r=  r-  rë   r.  r/  r0  )r.   r  r  rÊ   r­   r!   r0  )r3   r²   r=  r¿   r¸   rë   r/  s          r4   Ú_family_payloadrD  ®  sÀ   € Ø
�,‰,ÐLÈtÐUXÈkÓ
Z×
cÑ
cÓ
e€CØ(,¯©ØIÈDÈ7ó)Tö U 1ˆq�‹ð U€Gð Uä�d—l‘lØ
7¼°zÀÐJÈTÈGóUßU]ÑU]ÓU_óa€Jà 3 w¡<¸GÔSVÐW^ÓS_ÐbcÑScØ$´-×2HÑ2HÈÈdÓ2SñUð Uùò	Us   ¸B$c           	      óŽ  — t        ||d«      }|j                  }| j                  xs dj                  «       xs d}|r"t        j                  |«      st        dd¬«      ‚t        «       5 }t        ||| j                  «      }| j                  ry| j                  j                  «       j                  «       }	t        j
                  d|	«      st        dd¬«      ‚|j                  d	|	f«      j                  «       rt        d
d¬«      ‚t        ||«      }	|j                  d|	d|t!        «       |f«       |D ]  }
|j                  d|	|
f«       Œ |j#                  «        t%        |||	«      }ddd«       t'        j(                  |«       t+        j,                  d|j.                  t1        |«      |	dœ¬«       diS # 1 sw Y   ŒRxY w)zŸAttach a new family code to its class(es). One code per family, whatever the number of children:
    the individual and the sibling photos all end up under it.r_   r^   Nrƒ   úInvalid e-mail address.rj   z[A-Z0-9][A-Z0-9-]{2,22}zInvalid code.z#SELECT 1 FROM families WHERE code=?r›  zThis code already exists.zYINSERT INTO families (code, family_name, email, created_at, school_id) VALUES (?,?,?,?,?)ú;INSERT INTO students (family_code, class_name) VALUES (?,?)zfamily.create©r=  rë   r�  r±  )rŠ   rf   r-  rm   Ú	_EMAIL_OKÚ	fullmatchr
   r   rA  rë   r=  rª   r•  r.   r  rš   r   r/   rD  r"   r¹  r   rp   rG   r   )rU   rV   r{   r|   r-  r²   r-  r3   rë   r=  rx  r±  s               r4   Úcreate_familyrK  ¸  s•  € ô
 �- ¨:Ó
6€CØ
�-‰-€CØ�Z‰ZÒ˜2×$Ñ$Ó&Ò.¨$€EÙ”Y×(Ñ(¨Ô/Ü˜CÐ(AÔBÐBÜ	‹ð 2�TÜ   s¨D¯L©LÓ9ˆØ�9Š9Ø—9‘9—?‘?Ó$×*Ñ*Ó,ˆDÜ—<‘<Ð :¸DÔAÜ# C°Ô@Ð@Ø�|‰|ÐAÀDÀ7ÓK×TÑTÔVÜ# CÐ0KÔLÐLä" 4¨Ó-ˆDØ�‰ÐpØ˜B ¤z£|°SÐ9ô	;àò 	eˆCØ�L‰LÐVÐY]Ð_bÐXcÕdð	eà�‰ŒÜ   s¨DÓ1ˆ÷2ô" ×+Ñ+¨CÔ0Ü×Ñ˜O°S·X±XÌ)ÐT[ÓJ\ÐhkØ-1¸gÑ$FõHà�fÐÐ÷)2ð 2ús   Á*D F;Æ;Gc           	      óP  — t        ||d«      }|j                  }| j                  «       j                  «       } t	        «       5 }|j                  d| |f«      j                  «       st        dd¬«      ‚|j                  �Et        |||j                  «      }|j                  d| f«       |D ]  }	|j                  d| |	f«       Œ d	|j                  v rZ|j                  xs d
j                  «       xs d}
|
r"t        j                  |
«      st        dd¬«      ‚|j                  d|
| f«       |j                  «        t        ||| «      }ddd«       t!        j"                  |«       t%        j&                  d|j(                  t+        |«      || d   dœ¬«       d|iS # 1 sw Y   ŒUxY w)zSRe-attach a code to other classes (the sibling case, or a mistake at the shooting).r_   r®  r‚   r¯  rj   Nr°  rG  r-  r^   rƒ   rF  z(UPDATE families SET email=? WHERE code=?zfamily.updaterë   rH  r�  r±  )rŠ   rf   rm   rª   r   r.   r  r
   rë   rA  r¦  r-  rI  rJ  r/   rD  r"   r¹  r   rp   rG   r   )r=  rU   rV   r{   r|   r-  r²   r3   rë   rx  r-  r±  s               r4   Úupdate_familyrM  Ù  sˆ  € ô �- ¨:Ó
6€CØ
�-‰-€CØ�:‰:‹<×ÑÓ€DÜ	‹ð 2�TØ�|‰|ÐQÐTXÐZ]ÐS^Ó_×hÑhÔjÜ Ð,?Ô@Ð@Ø�<‰<Ð#Ü$ T¨3°·±Ó=ˆGØ�L‰LÐCÀdÀWÔMØò i�Ø—‘ÐZÐ]aÐcfÐ\gÕhðià�d×+Ñ+Ñ+Ø—Z‘ZÒ% 2×,Ñ,Ó.Ò6°$ˆEÙœY×0Ñ0°Ô7Ü# CÐ0IÔJÐJØ�L‰LÐCÀeÈTÀ]ÔSØ�‰ŒÜ   s¨DÓ1ˆ÷2ô ×+Ñ+¨CÔ0Ü×Ñ˜O°S·X±XÌ)ÐT[ÓJ\ÐhkØ-1¸fÀYÑ>OÑ$PõRà�fÐÐ÷%2ð 2ús   ÁDFÆF%)ÚfrÚenÚesr"  Úexplicitc                 ó`  — | j                  d«      xs |xs dj                  «       }|r|j                  d«      sd|z   }t        j                  d|«      sI|j
                  j                  › d|j                  j                  d|j
                  j                  «      › �}|j                  d«      S )	zePortal origin printed in the QR: the school's setting wins, then what the operator's browser reports.Ú
portal_urlr^   Úhttpzhttps://z#^https?://[^\s/]+(:\d+)?(/[^\s]*)?$z://ÚhostrÚ  )
rˆ   rm   r„   r•  ÚmatchÚurlÚschemer(  ÚnetlocÚrstrip)r"  rV   rQ  rW  s       r4   rÈ  rÈ  ú  s�   € à�<‰<˜Ó%Ò7¨Ò7°R×
>Ñ
>Ó
@€CÙ
�3—>‘> &Ô)Ø˜3ÑˆÜ�8‰8Ð:¸CÔ@Ø—‘×#Ñ#Ð$ C¨¯©×(;Ñ(;¸FÀGÇKÁK×DVÑDVÓ(WÐ'XÐYˆØ�:‰:�c‹?ÐrF   Úlangc                 ó  — | j                  d||f«      j                  «       }|sy | j                  d|f«      D �cg c]  }|d   ‘Œ	 }}t        j                  |«      }t	        j
                  ||d   «      D �	cg c]&  }	t	        j                  |	j                  «      dk(  r|	‘Œ( }
}	t        j                  |d   ||j                  d«      xs d||j                  d«      xs d	||j                  d
«      xs d|
¬«      S c c}w c c}	w )Nz6SELECT code FROM families WHERE code=? AND school_id=?rC  r,   r=  ÚoriginalrA  r^   Ú	rgpd_daysÚ60Úbrand_color)r=  rë   rA  rS  r^  r[  r`  r»  )r.   r  r%   r,  r"   Úlist_family_filesÚ
photo_kindr§   r    Úrender_cardrˆ   )r3   r²   r=  r[  r½  r  r¸   rë   Ústrz  r»  s              r4   Ú_render_family_cardre    s  € Ø
�,‰,ÐOÐRVÐX[ÐQ\Ó
]×
fÑ
fÓ
h€CÙØØ(,¯©ØIÈDÈ7ó)Tö U 1ˆq�‹ð U€Gð Uä	×	$Ñ	$ SÓ	)€Bä&×8Ñ8¸¸cÀ&¹kÓJö A�AÜ×)Ñ)¨!¯&©&Ó1°ZÒ?ò ð A€Fð Aä×#Ñ#Ø�‰[ 'Ø—F‘F˜=Ó)Ò/¨R¸DÈBÏFÉFÐS^ÓL_ÒLgÐcgÐnrØ—F‘F˜=Ó)Ò/¨R¸ô@ð @ùòUùòAs   »C?Á9+Dz&/api/media/card/{school_id}/{code}.pngrN  rL   Úsigc                 óè   ‡ ‡‡‡‡— t        j                  ‰ ‰||«      st        dd¬«      ‚‰t        v r‰ndŠdt        t
           fˆˆˆˆˆ fd„} |«       }|€t        dd¬«      ‚t        |d	d
di¬«      S )Nr�   zLink expired or invalid.rj   rN  r)   c                  óž   •— t        «       5 } t        | ‰‰‰t        t        j                  ‰«      ‰‰«      «      cd d d «       S # 1 sw Y   y xY wrù   )r   re  rÈ  r%   r,  )r3   r½  r=  r[  rV   rf   s    €€€€€r4   Úbuildzfamily_card_png.<locals>.build  sN   ø€ Ü‹Xð 	l˜Ü& t¨Y¸¸dÜ'3´N×4OÑ4OÐPYÓ4ZÐ\cÐeiÓ'jól÷	l÷ 	lò 	lús   Œ-AÁAr‚   r¯  z	image/pngr%  zprivate, max-age=300©Úcontentr'  r(  )r!   Úcheck_card_signaturer
   Ú
CARD_LANGSr   r  r   )	rf   r=  rV   rL   rf  r[  r½  ri  Úpngs	   ```  ``  r4   Úfamily_card_pngro    s~   ü€ ô ×-Ñ-¨i¸¸sÀCÔHÜ˜CÐ(BÔCÐCØœ:Ñ%‰4¨4€Dðl”8œE‘?÷ lñ lñ ‹'€CØ
€{Ü˜CÐ(;Ô<Ð<Ü˜C¨KÀ/ÐSiÐAjÔkÐkrF   z/api/admin/cards.zipc           
      óR  ‡ ‡‡‡‡
— t        ||d«      }|j                  Š
‰t        v r‰ndŠdt        t        t
        f   fˆˆˆˆ ˆ
fd„} |«       \  }}	|	dk(  rt        dd¬«      ‚t        j                  d	|j                  t        ‰ «      ‰
|	‰‰d
œ¬«       t        |ddd‰
› d�i¬«      S )zVEvery card of the school (or one class) as PNG files, grouped in one folder per class.r_   rN  r)   c                  ó$  •‡— t        j                  ‰«      } t        | ‰‰«      }t        j                  «       }d}t        «       5 }t        |‰«      Š|j                  d‰f«      j                  «       }t        ˆfd„|D «       d„ ¬«      }i }t        j                  |dt        j                  «      5 }|D ]   \  }	}
|
r|
d   d   nd}‰r|‰k7  rŒt        |‰|	d	   ‰|«      }|sŒ/t        t        j                   d
d|«      «      j#                  d«      xs d}|j%                  |d«      dz   ||<   |j'                  |› d||   d›d|	d	   › d�|«       |dz  }Œ¢ 	 d d d «       d d d «       |j)                  «       |fS # 1 sw Y   Œ#xY w# 1 sw Y   Œ'xY w)Nr   zQSELECT code, created_at FROM families WHERE school_id=? ORDER BY created_at, codec              3   óL   •K  — | ]  }|‰j                  |d    g «      f–— Œ y­w)r=  NrÚ   )r‘   r¸   rº   s     €r4   r“   z2family_cards_zip.<locals>.build.<locals>.<genexpr>8  s'   øè ø€ ÒD¸!�!�\×%Ñ% a¨¡i°Ó4Ô5ÑDùs   ƒ!$c                 ó^   — t        | d   r| d   d   d   nd«      | d   d   xs d| d   d   fS )Nr©   r   r,   r2  ré   r^   r=  r}  )Úrss    r4   r€  z1family_cards_zip.<locals>.build.<locals>.<lambda>9  sF   € ¤ÈÈAÊ¨r°!©u°Q©x¸Ò/EÐTWÓ XÐZ\Ð]^ÑZ_Ð`lÑZmÒZsÐqsØ " 1¡ f¡ð /€ rF   r¼   Úwr,   zno-classr=  z[^\w.-]+Ú_r©   rÚ  Ú03dz.png)r%   r,  rÈ  r
  r  r   r¹   r.   rË   r¾   ÚzipfileÚZipFileÚ
ZIP_STOREDre  r—   r•  r–  rm   rˆ   Úwritestrr  )rd  rÊ  Úbufr  r3   rT  ÚorderedÚseqÚzfr¸   Ústudsrx  rn  Úfolderrº   r½  r,   r[  rV   r²   s                 @€€€€€r4   ri  zfamily_cards_zip.<locals>.build.  sš  ù€ Ü×(Ñ(¨Ó-ˆÜ˜b '¨4Ó0ˆÜ�j‰j‹lˆØˆÜ‹Xð 	˜Ü.¨t°SÓ9ˆLØ—<‘<Ð sØ!$ ó(ß(0©«
ð äÛD¸tÔDñ/ô0ˆGð #%ˆCÜ—‘  c¬7×+=Ñ+=Ó>ð 
À"Ø 'ò 	‘H�A�uÙ49˜% ™( <Ò0¸z�CÙ! c¨ZÒ&7Ø Ü-¨d°C¸¸6¹ÀDÈ&ÓQ�CÚÜ!/´·±°{ÀCÈÓ0MÓ!N×!TÑ!TÐUXÓ!YÒ!gÐ]g˜Ø&)§g¡g¨f°aÓ&8¸1Ñ&<˜˜F™ØŸ™ v h¨a°°F±¸CÐ/@ÀÀ!ÀFÁ)ÀÈDÐ$QÐSVÔWØ˜Q™™ñ	÷
÷	ð( �|‰|‹~˜qÐ Ð ÷
ð 
ú÷	ð 	ús,   ÁA,FÂ11E:Ã#A4E:ÅFÅ:F	Å?FÆFr   r‚   zNo cards to export.rj   zcards.export)Úcardsrž  r[  r�  zapplication/zipúContent-Dispositionz%attachment; filename="meeconic_cards_z.zip"rj  )rŠ   rf   rm  Útupler  r$  r
   r   rp   rG   r   r   )rV   r[  r,   r½  r{   r|   r-  ri  r  r  r²   s   ````      @r4   Úfamily_cards_zipr…  %  s»   ü€ ô
 �- ¨:Ó
6€CØ
�-‰-€CØœ:Ñ%‰4¨4€Dð!”5œ¤˜Ñ$÷ !ñ !ñ6 ‹g�G€Dˆ!ØˆA‚vÜ˜CÐ(=Ô>Ð>Ü×Ñ˜N°C·H±HÌÐSZÓI[ÐgjØ./¸*ÈdÑ$SõUä˜DÐ->Ø2Ð6[Ð\_Ð[`Ð`eÐ4fÐgôið irF   r-  c                 ó   — | j                   S )uR   Directeur : toujours son Ã©cole. OpÃ©rateur : l'Ã©cole sÃ©lectionnÃ©e, sinon tout.)rf   )r-  s    r4   Ú_audit_scoper‡  T  s   € à�=‰=ÐrF   z/api/admin/auditrF  iô  )r�  )r�  Úactionra   Ú	date_fromÚdate_toÚlimitÚoffsetc           	      ó®   — t        ||ddd¬«      }t        |«      }	t        j                  | ||||	||¬«      }
t        j                  | ||||	¬«      }|
|dœS )Nr_   re   Fry   )rˆ  ra   r‰  rŠ  rf   r‹  rŒ  )rˆ  ra   r‰  rŠ  rf   )Úeventsrä   )rŠ   r‡  r   Úlist_eventsÚcount_events)rˆ  ra   r‰  rŠ  r‹  rŒ  r{   r|   r-  r²   rŽ  rä   s               r4   Ú	get_auditr‘  Y  sj   € ô �- ¨:°zÈuÔ
U€CÜ
�sÓ
€CÜ×&Ñ&¨f¸xÐS\ÐfmØ14¸EÈ&ôR€Fä×&Ñ&¨f¸xÐS\ÐfmØ14ô6€Eà uÑ-Ð-rF   z/api/admin/audit/export.csvc                 óð  — t        ||ddd¬«      }t        j                  | |||t        |«      d¬«      }t	        j
                  «       }t        j                  |d¬«      }	|	j                  g d	¢«       |D ]_  }
|	j                  |
d
   |
d   xs d|
d   |
d   xs d|
d   xs d|
d   xs d|
d   rt        j                  |
d   d¬«      nd|
d   g«       Œa t        d|j                  «       z   dddi¬«      S )Nr_   re   Fry   r�  )rˆ  ra   r‰  rŠ  rf   r‹  ú;©Ú	delimiter)Údate_utcÚutilisateurrˆ  Útype_ressourceÚid_ressourcerr   rh   Úniveauré   r`   r^   rˆ  r  r  r]   rh   )Úensure_asciira   õ   ï»¿útext/csv; charset=utf-8rƒ  z)attachment; filename="meeconic_audit.csv"rj  )rŠ   r   r�  r‡  r
  ÚStringIOÚcsvÚwriterÚwriterowrí   Údumpsr   r  )rˆ  ra   r‰  rŠ  r{   r|   r-  rŽ  r|  ru  r  s              r4   Úexport_audit_csvr£  l  s  € ô �- ¨:°zÈuÔ
U€CÜ×&Ñ&¨f¸xÐS\ÐfmÜ1=¸cÓ1BÈ$ôP€Fä
�+‰+‹-€CÜ�
‰
�3 #Ô&€AØ‡J�JÒqÔrØò ˆØ	�
‰
Øˆl‰O˜Q˜y™\Ò/¨R°°8±¸aÀÑ>PÒ>VÐTVØˆmÑÒ"  A l¡OÒ$9°rØ<=¸iºLŒD�J‰J�q˜‘|°%Õ8ÈbÐRSÐT^ÑR_ð
õ 	ðô Ø˜Ÿ™›Ñ&Ð3LØ&Ð(SÐTôð rF   c                 ó<   — | €dn
t        | «      }|dd dv rd|z   S |S )zGNeutralise l'injection de formules Excel (=, +, -, @) dans les exports.Nr^   r©   )ú=ú+ú-ú@ú')rC   )r/  r‹   s     r4   Ú	_csv_saferª  ˆ  s.   € àˆi‰œS ›V€AØ˜˜�eÐ3Ñ3ˆ3�‰7Ð:¸Ð:rF   z/api/admin/export/printer.csvc                 óÀ  — t        ||d«      }t        «       5 }t        ||j                  «      }t	        ||j                  d«      }d d d «       t        j                  «       }t        j                  |d¬«      }|j                  g d¢«       D ]¡  }	t        |	«      }
| r| |
d   vrŒ|
d   D ]�  }|j                  |
d   |
d	   t        d
j                  |
d   «      «      |d   t        |d   «      |d   |j                  d«      rdndt        |j                  d«      xs d«      |
d   g	«       Œƒ Œ£ t        d|j                  «       z   dddi¬«      S # 1 sw Y   �ŒxY w)Nr_   úORDER BY created_atr“  r”  )	ÚcommandeÚcode_famillerë   Úfichier_photoÚformatÚquantiteÚ
magique_iaÚ	metier_iaÚ
statut_lotrë   rÍ   rê   r´   z / Úphoto_filenameÚformat_labelrÑ   rÓ   ÚouiÚnonÚ	job_labelr^   rç   rœ  r�  rƒ  z.attachment; filename="meeconic_imprimerie.csv"rj  )rŠ   r   r¹   rf   rÌ   r
  rž  rŸ  r   r¡  rõ   rª  r•   rˆ   r   r  )r,   r{   r|   r-  r3   rº   rT  r|  ru  r¸   r  rÛ   s               r4   Úexport_printer_csvrº  Ž  si  € ô �- ¨:Ó
6€CÜ	‹ð G�TÜ*¨4°·±Ó?ˆÜ˜4 §¡Ð0EÓFˆ÷Gô �+‰+‹-€CÜ�
‰
�3 #Ô&€AØ‡J�Jò Oô Pàò 
ˆÜ˜q ,Ó/ˆÙ˜*¨A¨i©LÑ8ØØ�G‘*ò 	ˆBØ�J‰JØ�*‘˜q Ñ/´¸5¿:¹:ÀaÈ	ÁlÓ;SÓ1TØÐ#Ñ$¤i°°>Ñ0BÓ&CÀRÈ
Á^ØŸ™ 
Ô+‘°¼	À"Ç&Á&ÈÓBUÒB[ÐY[Ó8\Ø�.Ñ!ð	õ ñ	ð	
ô Ø˜Ÿ™›Ñ&Ð3LØ&Ð(XÐYôð ÷'Gñ Gús   ˜.EÅEz/api/admin/export/labelsc                 ó„  — t        ||d«      }t        «       5 }t        ||j                  «      }t	        ||j                  d«      }d d d «       i }D ]€  }t        |«      }	|	d   D ]j  }
| r|
| k7  rŒ|j                  |
|
t        «       dg dœ«      }|d   j                  |	d   «       |d   j                  |
|	d	   |	d
   |	d   |	d   |	d   dœ«       Œl Œ‚ g }t        |j                  «       d„ ¬«      D ]<  }t        d„ |d   D «       «      |d<   t        |d   «      |d<   |j                  |«       Œ> d|iS # 1 sw Y   ŒñxY w)Nr_   r¬  rë   r   )r,   r+   rØ   Úlabelsr+   rã   r¼  rê   r´   rì   rØ   rÙ   )r,   rf  r´   ÚsummaryrØ   rÙ   c                 ó   — t        | d   «      S r|  r}  r~  s    r4   r€  zexport_labels.<locals>.<lambda>Á  ó   € ´^ÀAÀlÁOÓ5T€ rF   r¼   c              3   ó&   K  — | ]	  }|d    –— Œ y­w©rØ   NrE   )r‘   Úls     r4   r“   z export_labels.<locals>.<genexpr>Â  s   è ø€ Ò;¨!˜!˜H�+Ñ;ùrÄ   )rŠ   r   r¹   rf   rÌ   rõ   rµ   rN  rP  r¶   r¾   r‚  rï   r­   )r,   r{   r|   r-  r3   rº   rT  Úby_classr¸   r  r’   Úgr·   s                r4   Úexport_labelsrÅ  «  sl  € ô �- ¨:Ó
6€CÜ	‹ð G�TÜ*¨4°·±Ó?ˆÜ˜4 §¡Ð0EÓFˆ÷Gð !#€HØò ˆÜ˜q ,Ó/ˆØ�9‘ò 		ˆAÙ˜a :šoØØ×#Ñ# A°aÄ3Ã5ÐTUÐacÑ'dÓeˆAØˆh‰K�O‰O˜A˜d™GÔ$Øˆh‰K×ÑØØ˜j™M¸!¸MÑ:JØ˜_Ñ-¸¸8¹ÈAÈdÉGñ õ ñ		ðð €CÜ�H—O‘OÓ%Ñ+TÔUò ˆÜÑ;¨q°©{Ô;Ó;ˆˆ(‰Ü˜!˜H™+Ó&ˆˆ(‰Ø�
‰
�1�ðð �sÐÐ÷-Gð Gús   ˜.D6Ä6D?z/api/admin/export/delivery-slipc           
      óX  — t        | |d«      }t        «       5 }t        j                  |j                  |«      }t        ||j                  «      }t        ||j                  d«      }ddd«       i }D ]w  }t        |«      }	|	d   D ]a  }
|j                  |
|
t        «       dg dœ«      }|d   j                  |	d   «       |d	   j                  |	d
   |	d   |	d   |	d   |	d   dœ«       Œc Œy g }dx}}t        |j                  «       d„ ¬«      D ]U  }t        d„ |d	   D «       «      }|j                  |d   t        |d   «      ||d	   dœ«       |t        |d   «      z  }||z  }ŒW j!                  d«      |j!                  d«      t#        «       t        |«      |||dœS # 1 sw Y   �Œ;xY w)u\   Bordereau de livraison Ã  l'Ã©cole : liste de vÃ©rification par classe + ligne de signature.r_   r¬  Nrë   r   )r,   Ú	order_idsrØ   rÍ   rÇ  rã   rÍ   r´   rê   rì   rØ   rÙ   )r´   rf  r½  rØ   rÙ   c                 ó   — t        | d   «      S r|  r}  r~  s    r4   r€  z&export_delivery_slip.<locals>.<lambda>Þ  r¿  rF   r¼   c              3   ó&   K  — | ]	  }|d    –— Œ y­wrÁ  rE   râ   s     r4   r“   z'export_delivery_slip.<locals>.<genexpr>ß  s   è ø€ Ò5 Q�Q�x•[Ñ5ùrÄ   r,   )r,   r+   rØ   rÍ   rA  Údelivery_week)rA  rÊ  Úgenerated_atÚtotal_classesÚtotal_ordersÚtotal_printsrë   )rŠ   r   r%   r,  rf   r¹   rÌ   rõ   rµ   rN  rP  r¶   r¾   r‚  rï   r­   rˆ   r   )r{   r|   r-  r3   r"  rº   rT  rÃ  r¸   r  r’   rÄ  rë   rÍ  rÎ  rØ   s                   r4   Úexport_delivery_sliprÏ  È  s×  € ô �- ¨:Ó
6€CÜ	‹ð G�TÜ!×.Ñ.¨s¯}©}¸dÓCˆÜ*¨4°·±Ó?ˆÜ˜4 §¡Ð0EÓFˆ÷Gð
 !#€HØò ˆÜ˜q ,Ó/ˆØ�9‘ò 	ˆAØ×#Ñ# A°aÄcÃeÐWXÐceÑ'fÓgˆAØˆk‰N×Ñ˜q ™wÔ'Øˆg‰J×ÑØ  Ñ/Ø˜j™M°a¸Ñ6HÐTUÐV^ÑT_ÐghÐimÑgnñõ ñ	ðð €GØ"#Ð#€L�<Ü�H—O‘OÓ%Ñ+TÔUò ˆÜÑ5¨!¨G©*Ô5Ó5ˆØ�‰ a¨¡oÄÀQÀ{Á^ÓATÐ`fÐqrÐszÑq{Ñ|Ô}Øœ˜A˜k™NÓ+Ñ+ˆØ˜Ñ‰ð	ð #Ÿ,™, }Ó5Ø"Ÿ,™, Ó7Ü$›,Ü˜g›,Ø&Ø&Ø!ñð ÷1Gñ Gús   ˜AFÆF)z/api/admin/ingestionc           	      ó„  — t        | |d«      }ddlm}m} ddi ||j                  «      ¥}t        j                  |j                  «      j                  dd«      }||d   t         ||j                  «      j                  «      xs d	t        j                  t        j                  |t        t        j                  «      d
œS )Nr_   r   )Úget_organizer_statusÚincoming_dir_forÚ	availableTÚftp_urlr^   Úpending_countÚincoming)Ú	organizerÚincoming_pendingÚincoming_dirÚftp_hostÚftp_portrÔ  Úai_configured)rŠ   Úservices.organizer_servicerÑ  rÒ  rf   r%   r,  rˆ   rC   r§   r   ÚFTP_HOSTÚFTP_PORTr  ÚSTORYFACE_API_KEY)r{   r|   r-  rÑ  rÒ  r×  rÔ  s          r4   Úadmin_ingestionrá  ñ  sž   € ä
�- ¨:Ó
6€CßQØ˜dÐJÑ&:¸3¿=¹=Ó&IÐJ€IÜ×)Ñ)¨#¯-©-Ó8×<Ñ<¸YÈÓK€Gà%Ø% oÑ6ÜÑ 0°·±Ó ?× DÑ DÓEÒSÈÜ"ŸO™OÜ"ŸO™OØ#Ü ¤×!9Ñ!9Ó:ñð rF   z/api/admin/organizer/statusc                 óú   — t        | |d«      }ddlm}  ||j                  «      }	 t	        j
                  |j                  «      |d<   |S # t        $ r&}t        j                  d|«       d|d<   Y d}~|S d}~ww xY w)u—  Estado del organizer DE ESTE COLEGIO con su diagnÃ³stico de salud (lo sondea el banner del admin).

    `stalled` = el bucle muriÃ³, o hay fotos esperando y nada se ha movido en ORGANIZER_STALL_SECS. Va tambiÃ©n
    `ai`: la salud de los servicios de IA / protecciÃ³n de caras. Es el mismo tipo de informaciÃ³n (quÃ© falla
    en segundo plano) y este sondeo ya existe, asÃ­ que no hace falta otro endpoint.r_   r   )rÑ  rÙ   zSalud de IA no disponible: %sN)	rŠ   rÝ  rÑ  rf   r   Ú
get_statusrS  r1   rg   )r{   r|   r-  rÑ  rå   Úexcs         r4   Úorganizer_statusrå    sv   € ô �- ¨:Ó
6€CÝ?Ù! #§-¡-Ó0€FðÜ ×+Ñ+¨C¯M©MÓ:ˆˆt‰ð €Møô ò Ü�‰Ð3°SÔ9Øˆˆt�Ø€Mûðús   §"A Á	A:ÁA5Á5A:z/api/admin/setupc                 ó°  — ddl m} t        | |d«      }|j                  }t	        j
                  |«      }ddl m} t        «       5 }|j                  d|f«      j                  «       d   }|j                  d|f«      j                  «       d   }	ddd«       t        t        j                  |«      «      }
|j                  d	«      d
k(  }|r dt        |j                  |«      «      dddœgn
ddkD  dddœg}dt        |j                  d«      «      dddœdt        |j                  d«      «      dddœd	dkD  dddœg|¢d|
dkD  dddœ‘dt        |j                  d«      «      dddœ‘dt        t         j"                  xr t         j$                  «      dddœ‘d|j'                  «       dddœ‘}|t)        d„ |D «       «      t        |«      dœS # 1 sw Y   �Œ?xY w)zßFirst-run checklist for the selected school: what is configured, what is still missing, in the order to do it.

    `section` tells the UI which Settings tab / tab to open (settings sub-tabs: school, sales, photos, access).r   )Úemail_servicer_   r¿  r  r  z:SELECT COUNT(*) AS n FROM school_classes WHERE school_id=?NrÁ  rÂ  Ú
shoot_linkr"  r»  )rã   ÚdoneÚtabÚsectionr  r‚  rA  r?   rS  rë   ÚphotographersÚ
shoot_dateÚstripeÚaccessr-  c              3   ó&   K  — | ]	  }|d    –— Œ y­w)ré  NrE   )r‘   r‹   s     r4   r“   z(admin_setup_checklist.<locals>.<genexpr>6  s   è ø€ Ò'A°a¨¨&­	Ñ'AùrÄ   )Ústepsré  rä   )rÆ  rç  rŠ   rf   r%   r,  rÀ  r   r.   r  r­   r#   Úlist_photographersrˆ   r  rÇ  r   ÚSTRIPE_SECRET_KEYÚSTRIPE_WEBHOOK_SECRETÚis_configuredrï   )r{   r|   rç  r-  r²   rd  rÀ  r3   r  rë   rì  Úshoot_onÚfamily_stepsrñ  s                 r4   Úadmin_setup_checklistrø    s  € õ
 'Ü
�- ¨:Ó
6€CØ
�-‰-€CÜ	×	$Ñ	$ SÓ	)€BÝ&Ü	‹ð u�TØ—<‘<Ð VÐY\ÐX^Ó_×hÑhÓjÐknÑoˆØ—,‘,Ð[Ð^aÐ]cÓd×mÑmÓoÐpsÑtˆ÷uô Ô,×?Ñ?ÀÓDÓE€MØ�v‰v�lÓ# vÑ-€Hñ ð ¤d¨=×+BÑ+BÀ3Ó+GÓ&HÐQ[ÐhpÑ	qÑrà H¨q¡L¸ÈTÑ	RÐSð ð
 ¤d¨2¯6©6°-Ó+@Ó&AÈ*ÐaiÑjØ¤T¨"¯&©&°Ó*>Ó%?È
Ð_gÑhØ '¨A¡+°gÈ$ÑOð	ð 
ð		ð
 ¨¸Ñ(9À*ÐYaÑbð	ð ¤T¨"¯&©&°Ó*>Ó%?È
Ð_gÑhð	ð ¤¤f×&>Ñ&>Ò&_Ä6×C_ÑC_Ó!`Ðisð  AIñ  	Jð	ð  × ;Ñ ;Ó =ÀjÐ]eÑfð	€Eð ¤CÑ'A¸5Ô'AÓ$AÌCÐPUËJÑWÐW÷-uñ uús   ÁA	GÇGz/api/admin/systemc                 óz  — t        | |dd¬«       ddlm} ddlm}m} d}	 t        «       5 }|j                  d«      j                  «        d	d	d	«       d	}	 t        |j                  j                  d
«      d„ ¬«      }|r|d   j                  }	 t        j                  t         j"                  «      j$                  dz  }	t        «       5 }|j                  dt'        «       t)        d¬«      z
  j+                  «       f«      j                  «       d   }
d	d	d	«       t         j,                  |t/        t         j0                  «      t/        t         j2                  «      |j5                  «       t!        j6                  d«      t         j8                  xs d	t/        t         j:                  «      t/        t         j<                  «      t         j>                  dD �ci c]  }| ||«      “Œ c}||	
dœS # 1 sw Y   �Œ¡xY w# t        $ r d}Y �Œ²w xY w# t        $ r Y �Œ‡w xY w# t        $ r d	}	Y �Œhw xY w# 1 sw Y   �ŒxY wc c}w )uL   Vue d'ensemble technique pour l'opÃ©rateur (diagnostic sans accÃ¨s serveur).r_   Fry   r   )Ú	is_leader)Úbackup_servicerç  TzSELECT 1Nzmeeconic_*.sql.gzc                 ó6   — | j                  «       j                  S rù   )ÚstatÚst_mtimer3  s    r4   r€  zadmin_system.<locals>.<lambda>H  s   € Ð[\×[aÑ[aÓ[c×[lÑ[l€ rF   r¼   éÿÿÿÿi   zRSELECT COUNT(*) AS n FROM audit_logs WHERE severity='critical' AND created_at >= ?r©   ©rR  r  ÚEMAIL_ENABLED)ÚcleanupÚbackupÚmonitorr×  )ÚenvÚdb_okÚstripe_configuredÚstripe_webhook_configuredÚsmtp_configuredÚemail_enabledÚalerts_emailÚsentry_enabledrÜ  Úbackground_tasksÚthis_worker_is_leaderÚlast_backupÚdisk_free_mbÚcritical_events_24h) rŠ   Úcore.leaderrú  rÆ  rû  rç  r   r.   r  rS  r¾   ÚBACKUPS_DIRÚglobr§   ÚshutilÚ
disk_usager   Ú
PHOTOS_DIRÚfreer   r   rO  ÚAPP_ENVr  ró  rô  rõ  Ú_boolÚALERT_EMAILÚ
SENTRY_DSNrà  ÚBACKGROUND_TASKS_ENABLED)r{   r|   rú  rû  rç  r  r3   r  ÚfilesÚfree_mbÚcritr  s               r4   Úadmin_systemr!  9  sï  € ô ˆM˜8 Z¸UÕCÝ%ß6à€EðÜ‹Xð 	0˜Ø�L‰L˜Ó$×-Ñ-Ô/÷	0ð €KðÜ�~×1Ñ1×6Ñ6Ð7JÓKÑQlÔmˆÙØ ™)Ÿ.™.ˆKðÜ×#Ñ#¤F×$5Ñ$5Ó6×;Ñ;ÀÑLˆô 
‹ð [�TØ�|‰|ÐpÜ$›h¬¸Ô):Ñ:×EÑEÓGÐIóKßKSÉ8Ë:ÐVYñ[ˆ÷[ô �~‰~ØÜ!¤&×":Ñ":Ó;Ü%)¬&×*FÑ*FÓ%GØ(×6Ñ6Ó8ÜŸ™ oÓ6Ü×*Ñ*Ò2¨dÜœv×0Ñ0Ó1Üœf×6Ñ6Ó7Ü"×;Ñ;Ø;hÖ!i°a !¡Y¨q£\¡/Ò!iØ"ØØ#ñð ÷%	0ñ 	0ûäò Ø‹ðûô ò Úðûô ò Ø‹ðú÷[ñ [üò "js_   ¡
G7 « G*ÁG7 Á8H	 Â0H Ã	AH+ÇH8Ç*G4Ç/G7 Ç7HÈHÈ		HÈHÈH(È'H(È+H5z/api/director/dashboardc                 ó~  — t        | |dd«      }|j                  }t        «       5 }t        j                  ||«      }t        ||«      }|j                  d|f«      j                  «       d   }|j                  d|f«      j                  «       d   }t        ||«      }	d d d «       dx}
x}x}}t        «       }t        «       j                  «       j                  «       }d}d }D ]w  }t        t        j                  |d   «      «      }|
|d	   z  }
||d
   z  }||d   z  }||d   z  }|j!                  |d   «       |d   d d |k(  r|dz  }|�	|d   |kD  sŒs|d   }Œy d}	 t        j"                  d   «      t%        t'        |d   «      ¬«      z   j                  «       }t+        j-                  d«      xs d«      }i d|j-                  d«      “dt/        |«      “d|“dt1        |
«      “dt1        |«      “dt1        |«      “dt1        |«      “d|“dt1        |
|z  dz  «      “d|j-                  d«      “d rt1        dt/        |«      z  |z  «      nd“d!t/        |«      “d"|“d#“d$|“d%	“d&|j-                  d«      |j-                  d'«      |j-                  d(«      |d)œ“S # 1 sw Y   �ŒxY w# t(        $ r Y �Œw xY w)*Nr_   re   r  r  r	  rÏ   r   rà   rä   rÖ   r×   rÔ   r´   ré   é
   r©   r^   rí  r^  r   r@  rA  rB  Úorders_todayr  Úrevenue_paperÚrevenue_magicÚrevenue_digitalrM  rF  Úpartner_namer�  ru  rJ  rK  Úlast_order_atrë   Úcalendarr?  rÊ  )rí  r?  rÊ  Úrgpd_delete_date)rŠ   rf   r   r%   r,  rÌ   r.   r  r‡  rN  r   r   rO  rÞ   rí   rî   rP  rQ  r   r$  rS  r%  rˆ   r­   r  )r{   r|   r-  r²   r3   r"  rT  rU  rV  rë   r  rÖ   r×   rÔ   rX  ÚtodayÚtoday_countr)  r¸   rð   Úrgpd_deleter]  s                         r4   Údirector_dashboardr/  h  sM  € ä
�- ¨:°zÓ
B€CØ
�-‰-€CÜ	‹ð -�TÜ!×.Ñ.¨s°DÓ9ˆÜ˜4 Ó%ˆØ—\‘\Ð"XÐ[^ÐZ`Óa×jÑjÓlÐmpÑqˆ
Ø—\‘\ð #8Ø:=¸óAßAIÁÃÈCñQˆ
ä! $¨Ó,ˆ÷-ð ),Ð+€GÐ+ˆeÐ+�e˜gÜ‹U€FÜ‹H�M‰M‹O×%Ñ%Ó'€EØ€KØ€MØò 
,ˆÜœŸ™ A l¡OÓ4Ó5ˆØ�1�W‘:ÑˆØ��7‘ÑˆØ��7‘ÑˆØ�1�Y‘<ÑˆØ�
‰
�1�]Ñ#Ô$Øˆ\‰?˜3˜BÐ 5Ò(Ø˜1ÑˆKØÐ  A l¡O°mÓ$CØ˜l™O‰Mð
,ð €KðÜ×)Ñ)¨(°<Ñ*@ÓAÄIÔSVÐW_Ð`kÑWlÓSmÔDnÑn×yÑyÓ{ˆô �h—l‘lÐ#6Ó7Ò<¸1Ó=€IðØ˜(Ÿ,™, }Ó5ðàœ#˜d›)ðð 	˜+ðð 	œ% ›.ð	ð
 	œ% ›,ðð 	œ% ›,ðð 	œ5 ›>ðð 	˜Yðð 	œ% ¨)Ñ 3°cÑ 9Ó:ðð 	˜(Ÿ,™, ~Ó6ðð 	Á:œ% ¤c¨&£kÑ 1°JÑ >Ô?ÐSTðð 	œ#˜f›+ðð 	˜*ðð 	˜*ðð 	˜-ðð  	˜'ð!ð" 	Ø (§¡¨\Ó :Ø (§¡¨^Ó <Ø (§¡¨_Ó =Ø +ñ	
ð#ð ÷A-ñ -ûô: ò Úðús   ¥A7J"Å?J/ Ê"J,Ê/	J<Ê;J<z/api/qr.svgr©   i,  rÿ   é   é   rá  r  Úscalec                 óÂ   — dd l }|j                  | dd¬«      }t        j                  «       }|j	                  |d|dd¬«       t        |j                  «       d	d
di¬«      S )Nr   ÚmF)ÚerrorÚmicroÚsvgz#0A1F44r0  )Úkindr2  ÚdarkÚborderzimage/svg+xmlr%  r&  rj  )ÚsegnoÚmaker
  r  r  r   r  )r  r2  r;  rÃ  r|  s        r4   Úqr_svgr=  ¨  s^   € ãð 
�‰�D ¨5ˆÓ	1€BÜ
�*‰*‹,€Cà‡G�GˆC�e 5¨yÀ€GÔCÜ˜CŸL™L›N°Ø,Ð.EÐFôHð HrF   c                   ó"   — e Zd ZU eeef   ed<   y)ÚPhotographersBodyrì  N)r@   rA   rB   r#  rC   rD   rE   rF   r4   r?  r?  ·  s   … Ø˜˜S˜‘>Ô!rF   r?  z/api/admin/photographersc                 óœ   — t        | |d«      }t        j                  |j                  «      t        j                  |j                  «       dœS )Nr_   )rì  Úimplicit)rŠ   r#   rò  rf   Úget_photographer_map©r{   r|   r-  s      r4   Úget_photographersrD  »  sC   € ä
�- ¨:Ó
6€CÜ1×DÑDÀSÇ]Á]ÓSÜ0×EÑEÀcÇmÁmÓTÐTñVð VrF   c           	      óz  — t        ||d«      }	 t        j                  |j                  | j                  «      }t        j                  d|j                  t        |«      d|j                  d|id¬«       t        j                  |j                  «      |d	œS # t
        $ r}t        dt        |«      ¬«      ‚d }~ww xY w)
Nr_   rƒ   rj   zphotographer.updateÚphotographer_mapÚmaprg   )r`   r]   r  rf   rh   ra   )rì  rG  )rŠ   r#   Úupdate_photographer_maprf   rì  Ú
ValueErrorr
   rC   r   rp   rG   r   rò  )rU   rV   r{   r|   r-  Úupdatedr  s          r4   Úset_photographersrK  Â  s©   € ô �- ¨:Ó
6€Cð0Ü&×>Ñ>¸s¿}¹}Èd×N`ÑN`Óaˆô ×ÑÐ1¸3¿8¹8ÔPYÐZaÓPbØ*<ÈÏÉØ%*¨GÐ$4¸yõJô 2×DÑDÀSÇ]Á]ÓSÐ\cÑdÐdøô ò 0Ü˜C¬¨A«Ô/Ð/ûð0ús   �*B Â	B:ÂB5Â5B:z/api/admin/protectionc                 ó�   — t        | |dd«      }t        j                  |j                  «      t	        t
        j                  «      dœS )Nr_   re   )Ú
protectionÚheads_available)rŠ   r$   Úget_protectionrf   r  r   rà  rC  s      r4   rO  rO  Ò  s<   € ä
�- ¨:°zÓ
B€CÜ,×;Ñ;¸C¿M¹MÓJÜ#¤F×$<Ñ$<Ó=ñ?ð ?rF   c                 ón  — t        ||d«      }	 t        j                  |j                  | j	                  d| «      «      }t        j                  d|j                  t        |«      d|j                  |j                  |d   |d   d	œd
¬«       d|iS # t
        $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   rM  rƒ   rj   zprotection.updater?   Úprotection_heads_enabledÚprotection_overlay)ÚheadsÚoverlayrg   r5  )rŠ   r$   Úset_protectionrf   rˆ   rI  r
   rC   r   rp   rG   r   )rU   rV   r{   r|   r-  Úcfgr  s          r4   Úput_protectionrW  Ù  s´   € ô �- ¨:Ó
6€Cð0Ü ×/Ñ/°·±¸t¿x¹xÈÐVZÓ?[Ó\ˆô ×ÑÐ/¸¿¹ÌiÐX_ÓN`Ø*2ÀÇÁÐY\×YfÑYfØ.1Ð2LÑ.MÐZ]Ð^rÑZsÑ$tØ%.õ0ð ˜#ÐÐøô ò 0Ü˜C¬¨A«Ô/Ð/ûð0ús   �0B Â	B4ÂB/Â/B4ÚBORRARÚconfirmc                 ó~   — | xs dj                  «       j                  «       t        k7  rt        ddt        › �¬«      ‚y)z\Pestillo contra el borrado masivo accidental (un enlace, un script a medias, un doble clic).r^   rƒ   z$Confirmation required: send confirm=rj   N)rm   rª   ÚDESTRUCTIVE_CONFIRM_WORDr
   )rY  s    r4   Ú_require_confirm_wordr\  ï  s>   € àŠ�2×ÑÓ×$Ñ$Ó&Ô*BÒBÜ˜CÐ*NÔOgÐNhÐ(iÔjÐjð CrF   z/api/admin/photos/familiesc                 ó¤   — t        | |d«      }t        j                  |j                  «      }d|D �cg c]  }|j	                  «       ‘Œ c}iS c c}w )Nr_   r  )rŠ   r"   Úget_all_familiesrf   rò  )r{   r|   r-  Úfamsrz  s        r4   Úphotos_familiesr`  õ  sC   € ä
�- ¨:Ó
6€CÜ×)Ñ)¨#¯-©-Ó8€DØ°Ö6¨A˜Ÿ™�Ò6Ð7Ð7ùÒ6s   ²Az/api/admin/photos/by-datez^\d{4}-\d{2}-\d{2}$z^\d{2}:\d{2}$iè  Ú	time_fromÚtime_toÚphotographerc           	      óô   — t        ||d«      }t        j                  |j                  | |||||«      \  }	}
|	D ]0  }t	        j
                  |j                  |d   |d   |d   «      |d<   Œ2 |	t        |	«      |
dœS )Nr_   rc  Úqr_codeÚfilenameÚ	thumb_url)r»  rä   Útotal_matching)rŠ   r"   Úlist_photos_by_daterf   r!   Úadmin_thumb_urlr­   )r‰  rŠ  ra  rb  rc  r‹  r{   r|   r-  r»  rä   Úps               r4   Úphotos_by_daterl  ü  s•   € ô �- ¨:Ó
6€CÜ!×5Ñ5°c·m±mÀYÐPWÐYbÐdkÐmyð  |Aó  B�M€FˆEØò vˆÜ&×6Ñ6°s·}±}ÀaÈÑFWÐYZÐ[dÑYeÐghÐisÑgtÓuˆˆ+Šðvà¤s¨6£{ÀeÑLÐLrF   c                   ó^   — e Zd ZU eed<   eed<   eed<    edd¬«      Zeed<   dZee   ed	<   y)
ÚPhotoMoveBodyrc  re  rf  rè  r9  rÿ   Útarget_qr_codeNÚtarget_photographer)	r@   rA   rB   rC   rD   r   ro  rp  r   rE   rF   r4   rn  rn    s2   … ØÓØƒLØƒMÙ¨1¸Ô<€N�CÓ<Ø)-Ð˜ #™Ô-rF   rn  z/api/admin/photos/movec                 óv  — t        ||d«      }	 t        j                  |j                  | j                  | j
                  | j                  | j                  | j                  «      }t        j                  d|j                  t!        |«      d| j
                  › d| j                  › �|j                  d| j                  | j
                  d	œ| j                  xs | j                  |d	œd
œ¬«       d|dœS # t        $ r}t        dt        |«      ¬«      ‚d }~wt        $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   r‚   rj   rƒ   z
photo.moveÚphotorÚ  rg   )rc  re  r2  r  T)rn  re  )rŠ   r"   Ú
move_photorf   rc  re  rf  ro  rp  ÚFileNotFoundErrorr
   rC   rI  r   rp   rG   r   )rU   rV   r{   r|   r-  Úresolvedr  s          r4   Úphotos_moverv    s  € ô �- ¨:Ó
6€Cð0Ü ×+Ñ+¨C¯M©M¸4×;LÑ;LÈdÏlÉlØ+/¯=©=¸$×:MÑ:MÈt×OgÑOgóiˆô ×ÑØ˜cŸh™h´9¸WÓ3EÐU\Ø—|‘|�n A d§m¡m _Ð5ÀÇÁÐYbØ*.×*;Ñ*;ÈÏÉÑUØ(,×(@Ñ(@Ò(UÀD×DUÑDUÐbjÑkñmõnð
 ¨Ñ1Ð1øô ò 0Ü˜C¬¨A«Ô/Ð/ûÜò 0Ü˜C¬¨A«Ô/Ð/ûð0ús$   �AC2 Ã2	D8Ã;DÄD8ÄD3Ä3D8z/api/admin/photos/family/{code}c           
      ó.  — t        ||d«      }	 t        j                  |j                  | «      }t        j                  d|j                  t        |«      d| |j                  d|id¬«       d	|d
œS # t        $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   rƒ   rj   zphoto.delete_familyr±  r  rg   r5  T©rn  Údeleted)rŠ   r"   r¸  rf   rI  r
   rC   r   rp   rG   r   )r=  rV   r{   r|   r-  r  r  s          r4   Úphotos_delete_familyrz  '  s—   € ô �- ¨:Ó
6€Cð0Ü×.Ñ.¨s¯}©}¸dÓCˆô ×ÑÐ1¸3¿8¹8ÔPYÐZaÓPbØ*2ÀÐPS×P]ÑP]Ø%,¨a L¸9õFð ¨Ñ*Ð*øô ò 0Ü˜C¬¨A«Ô/Ð/ûð0úó   � A0 Á0	BÁ9BÂBz/api/admin/photos/allc           
      ó  — t        ||d«      }t        |«       t        j                  |j                  «      }t        j                  d|j                  t        | «      d|j                  |j                  d|id¬«       d|dœS )	Nr_   zphoto.delete_allr?   r  r´  r5  Trx  )	rŠ   r\  r"   Údelete_all_photosrf   r   rp   rG   r   )rV   rY  r{   r|   r-  r  s         r4   Úphotos_delete_allr~  5  sv   € ô �- ¨:Ó
6€CÜ˜'Ô"Ü×'Ñ'¨¯©Ó6€AÜ×ÑÐ.¸¿¹ÌYÐW^ÓM_Ø*2ÀÇÁÐY\×YfÑYfØ%,¨a L¸:õGð ¨Ñ*Ð*rF   c                 óP   ‡ ‡— ˆ ˆfd„} |d«       |dt         › �«       |d«      dœS )Nc                 óL   •— ‰j                  | ‰f«      j                  «       d   S )Nr  )r.   r  )Úsqlr3   r²   s    €€r4   r€  z$_sales_data_counts.<locals>.<lambda>B  s"   ø€ �d—l‘l 3¨¨Ó/×8Ñ8Ó:¸3Ñ?€ rF   z2SELECT COUNT(*) AS n FROM orders WHERE school_id=?z7SELECT COUNT(*) AS n FROM orders WHERE school_id=? AND r  )r+   rÌ   r  )rÊ   )r3   r²   Úones   `` r4   Ú_sales_data_countsrƒ  A  s6   ù€ Ü
?€CáÐJÓKÙÐTÔU]ÐT^Ð_Ó`ÙÐNÓOñð rF   z/api/admin/reset-datac                 óˆ   — t        | |d«      }t        «       5 }t        ||j                  «      cddd«       S # 1 sw Y   yxY w)zLWhat a reset would remove, so the confirmation dialog can show real numbers.r_   N)rŠ   r   rƒ  rf   r‰  s       r4   Úreset_data_previewr…  J  s<   € ô �- ¨:Ó
6€CÜ	‹ð 7�TÜ! $¨¯©Ó6÷7÷ 7ò 7ús	   ˜8¸AFr  c                 ó  — t        ||d«      }t        |«       |j                  }t        «       5 }t	        ||«      }|j                  d|f«       |r&|j                  d|f«       |j                  d|f«       |j                  «        ddd«       d}	|r*t        j                  |«      }	t        j                  |«       t        j                  d|j                  t        | «      |d|i ¥||	d	œ¥d
¬«       ddi|¥d|	i¥S # 1 sw Y   ŒxxY w)uÚ  Wipe the TEST data of ONE school before going live: every order (paid ones included â€” unlike
    `delete_family`, which protects the proof of a sale) and, with `families=true`, the family codes, their
    class links and the photos (moved to the restorable trash). Classes, products, prices, settings and the
    audit journal are kept: the journal is the only trace that this happened. Payments already taken in
    Stripe are NOT touched â€” refund or ignore them there.r_   z$DELETE FROM orders WHERE school_id=?zWDELETE FROM students WHERE family_code IN (SELECT code FROM families WHERE school_id=?)z&DELETE FROM families WHERE school_id=?Nr   zschool.reset_datar?   )Úfamilies_removedr³  r´  rµ  rn  Tr³  )rŠ   r\  rf   r   rƒ  r.   r/   r"   r}  r¹  r   rp   rG   r   )
rV   rY  r  r{   r|   r-  r²   r3   r7  r»  s
             r4   Ú
reset_datarˆ  R  s  € ô �- ¨:Ó
6€CÜ˜'Ô"Ø
�-‰-€CÜ	‹ð �TÜ# D¨#Ó.ˆØ�‰Ð;¸c¸VÔDÙØ�L‰LÐrÐuxÐtzÔ{Ø�L‰LÐAÀCÀ6ÔJØ�‰Œ÷ð €FÙÜ×0Ñ0°Ó5ˆÜ×/Ñ/°Ô4Ü×ÑÐ/¸¿¹ÌiÐX_ÓN`ÐloØ*2ÀØ$f vÐ$fÀ8Ð_eÒ$fØ%/õ1ð �tÐ@˜vÐ@Ð'7¸Ñ@Ð@÷ð ús   ¯AC>Ã>Dz2/api/admin/photos/{photographer}/{code}/{filename}rf  c           	      óP  — t        ||d«      }	 t        j                  |j                  ||«      }|st        dd¬«      ‚t        j                  d|j                  t        |«      d|› d|› �|j                  d	¬
«       ddiS # t        $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   rƒ   rj   r‚   zPhoto introuvable.zphoto.deleterr  rÚ  rg   )r`   r]   r  r  rf   ra   rn  T)rŠ   r"   Údelete_photorf   rI  r
   rC   r   rp   rG   r   )	rc  r=  rf  rV   r{   r|   r-  Úokr  s	            r4   Úphotos_delete_onerŒ  o  s©   € ô �- ¨:Ó
6€Cð0Ü×'Ñ'¨¯©°t¸XÓFˆñ Ü˜CÐ(<Ô=Ð=Ü×Ñ˜N°C·H±HÌÐSZÓI[ÐkrØ+/¨&°°(°Ð(<ÈÏÉÐ`iõkà�tÐÐøô ò 0Ü˜C¬¨A«Ô/Ð/ûð0ús   �!B Â	B%Â
B Â B%z/api/admin/photos/trashc                 ó€   — t        | |d«      }t        j                  |j                  «      }|t        j
                  dœS )Nr_   )ÚentriesÚretention_days)rŠ   r"   Ú
list_trashrf   r   ÚTRASH_RETENTION_DAYS)r{   r|   r-  rŽ  s       r4   Úphotos_trashr’  ~  s5   € ä
�- ¨:Ó
6€CÜ×&Ñ& s§}¡}Ó5€GØ´&×2MÑ2MÑNÐNrF   z*/api/admin/photos/trash/{entry_id}/restoreÚentry_idc           
      ó.  — t        ||d«      }	 t        j                  |j                  | «      }t        j                  d|j                  t        |«      d| |j                  d|id¬«       d	|d
œS # t        $ r}t        dt        |«      ¬«      ‚d }~ww xY w)Nr_   rƒ   rj   zphoto.trash_restoreÚtrash_batchÚrestoredrg   r5  T)rn  r–  )rŠ   r"   Úrestore_trash_entryrf   rI  r
   rC   r   rp   rG   r   )r“  rV   r{   r|   r-  r–  r  s          r4   Úphotos_restorer˜  …  s˜   € ô �- ¨:Ó
6€Cð0Ü ×4Ñ4°S·]±]ÀHÓMˆô ×ÑÐ1¸3¿8¹8ÔPYÐZaÓPbØ*7ÀXÐY\×YfÑYfØ%/°Ð$:ÀYõPð ¨Ñ2Ð2øô ò 0Ü˜C¬¨A«Ô/Ð/ûð0úr{  )r)   N)r^   )±Ú__doc__r  rŸ  r
  rí   ÚloggingÚosr•  r  r�   rx  Údataclassesr   Údatetimer   r   Útypingr   rO   Úfastapir   r   r	   r
   r   r   r   Úfastapi.responsesr   r   r   Úpydanticr   r   Úcorer   Úcore.dbr   r   r   Úcore.rate_limitr   rl   Úcore.securityr   r   r   r   Úcore.timeutilr   r   rÆ  r   r   r    r!   r"   r#   r$   r%   Úutils.code_generatorr&   Ú	getLoggerr1   rQ   rN   rÊ   Úrouterr5   r7   rC   rT   Úpostrv   rx   r  rŠ   r—   rš   r¬   r±   r#  r:  r¹   rÀ   rÅ   rÌ   rÞ   rõ   r$  rû   rý   r  rˆ   r  r  Úpatchr  r!  r0  Úputr:  r^  rc  re  rp  r‡  rŠ  rŒ  r’  r—  r¢  r¨  Údeleter¬  r¼  rË  rÎ  r"  r  rÒ  rÜ  rÞ  rã  ræ  rë  rõ  rú  r#  r*  r5  r7  r<  ÚcompilerI  rA  rD  rK  rM  rm  rÈ  r  re  ro  r…  r‡  r‘  r£  rª  rº  rÅ  rÏ  rá  rå  rø  r!  r/  r=  r?  rD  rK  rO  rW  r[  r\  r`  rl  rn  rv  rz  r~  rƒ  r…  rˆ  rŒ  r’  r˜  rE   rF   r4   ú<module>r¯     s]  ðñó8 Û 
Û 	Û Û Û 	Û 	Û Û Û Ý !ß $Ý ã 
ß V× VÑ Vß GÑ Gß %å ß 8Ñ 8Ý 9ß RÓ Rß ,÷÷ ó õ 6à€g×ÑÐ(Ó)€à€Ø€Ø<€á	‹€ó
7#ôx?�9ô ?ð
F�ð F˜h s™mð F°ó Fð ‡�ÐÓ ð?�lð ?¨Wò ?ó !ð?ð0 ÷ð ó ðð
 eiò /˜8 C™=ð /°H¸S±Mð /È3ð /Ð]að /Ðmpó /ð<b�cð b˜có bðQ Sð Q¨Só Qò \€ð˜ó ð 3ð ¨4°°T¸$±Z°Ñ+@ó ðk Tð k¨d°3©ió kð& #ð &¨$¨s©)ó &ñ
`˜3ð `¨#ð `°tó `ð
\˜˜T™
ð \ tó \ð,¨$ð °4ó ð<O˜Sð O¨¨c©ð O°só OôK�9ô KôK�)ô Kð ‡�Ð Ó!Ù06°t³ÑX^Ð_cÓXdñ  ¨¡ð ÈÐQTÉò ó "ðð ‡�Ð!Ó"ÙW]Ð^bÓWcÙ28¸³,ñ
˜ð 
¨wð 
ÀxÐPSÁ}ð 
Ø"*¨3¡-ò
ó #ð
ð ‡�Ð.Ó/á6<¸T³lÑ^dÐeiÓ^jñ˜Cð  {ð ¸Wð Ø&.¨s¡mðØNVÐWZÉmòó 0ðô$9�Yô 9ð ‡�Ð!Ó"Ù28¸³,ÑZ`ÐaeÓZfñ R (¨3¡-ð RÈ(ÐSVÉ-ò Ró #ðRð ‡�Ð!Ó"ÙagÐhlÓamÙ:@À»,ñ ð ¸ð ÐQYÐZ]ÑQ^ð Ø*2°3©-òó #ðð$ ‡�ÐÓÙ/5°d«|ÑW]Ð^bÓWcñ 3˜x¨™}ð 3ÀxÐPSÁ}ò 3ó  ð3ðp ‡�ÐÓ á % d£Ù˜¨Ô8Ù#)¨$£<É6ÐRVË<ñØ˜‘ðàðð ˜C‘=ðð <DÀC¹=òó !ðô(˜ô ð ‡�Ð,Ó-á<BÀ4»LÑdjÐkoÓdpñ@ ð @Ð,>ð @Èð @Ø,4°S©Mð@ØT\Ð]`ÑTaò@ó .ð@ð4-˜sð - t¨D¡zó -ð` ‡�Ð Ó!Ù17¸³ÑY_Ð`dÓYeñ A ¨#¡ð AÈÐRUÉò Aó "ðAôFˆiô Fô
F�ô Fð
-�sð -˜só -ð ‡�Ð!Ó"ÙQWÐX\ÓQ]Ù17¸³ñ^�wð ^¨ð ^ÀÈ#Áð ^Ø!)¨#¡ò^ó #ð^ð$ ‡�Ð-Ó.ÙciÐjnÓcoÙ17¸³ñ1˜3ð 1 jð 1¸7ð 1ÐS[Ð\_ÑS`ð 1Ø!)¨#¡ò1ó /ð1ð@ ‡�Ð.Ó/ÙQWÐX\ÓQ]Ù17¸³ñ˜3ð ¨ð ÀÈ#Áð Ø!)¨#¡òó 0ðð( ‡�Ð+Ó,ÙNTÐUYËlÙ28¸³,ñ>˜ð > gð >¸hÀs¹mð >Ø"*¨3¡-ò>ó -ð>ð6 ‡�ÐÓÙ8=¸dÈsÔ8SÙ5;¸D³\Ñ]cÐdhÓ]iñ˜ð ¨°©ð Ø%-¨c¡]ðØMUÐVYÉ]òó  ðð  ‡�Ð%Ó&ÙGMÈdÃ|Ù6<¸T³lñ˜wð °xÀ±}ð Ø&.¨s¡mòó 'ðð *Ð ØÐ ðG #ð G°3ó Gð Cð ¨Dó ô�	ô ô"�9ô "ð ‡�Ð!Ó"Ù17¸³ÑY_Ð`dÓYeñ K ¨#¡ð KÈÐRUÉò Kó #ðKð ‡�Ð"Ó#ÙU[Ð\`ÓUaÙ39¸$³<ñ2˜ð 2¨Wð 2ÀXÈcÁ]ð 2Ø#+¨C¡=ò2ó $ð2ð" ‡�Ð0Ó1á8>¸t»Ñ`fÐgkÓ`lñ2˜sð 2¨,ð 2Àð 2Ø(0°©ð2ØPXÐY\ÑP]ò2ó 2ð2ð8 ‡�Ð1Ó2ÙU[Ð\`ÓUaÙ39¸$³<ñ˜sð ¨Wð ÀXÈcÁ]ð Ø#+¨C¡=òó 3ðð& ‡�Ð5Ó6ÙUYÐZ]ÓU^Ù>DÀT»lÑflÐmqÓfrñ-7¨3ð -7¸ð -7È
ð -7Ø.6°s©mð-7ØV^Ð_bÑVcò-7ó 7ð-7ð` ‡�ÐEÓFðk˜Sð k¨cò kó Gðkð ‡�Ð!Ó"Ù&+¨D£kÑRXÐY]ÓR^Ù39¸$³<ñ0�h˜s‘mð 0À(È3Á-ð 0Ø#+¨C¡=ò0ó #ð0ô8=�9ô =ô?�)ô ?ð
 ˆB�JŠJÐ2Ó3€	ð˜cð ¨D°©Ið ¸$¸s¹)ó ðU˜sð U¨#ð U°$ó Uð ‡�Ð"Ó#ÙW]Ð^bÓWcÙ28¸³,ñ˜ð ¨wð ÀxÐPSÁ}ð Ø"*¨3¡-òó $ðð@ ‡�Ð*Ó+ÙagÐhlÓamÙ28¸³,ñ˜ð  ;ð ¸ð ÐQYÐZ]ÑQ^ð Ø"*¨3¡-òó ,ðð:  €
ð˜4ð ¨'ð ¸XÀc¹]ð Èsó ð@ 3ð @¨cð @¸ð @ÀCð @ÈHÐUZÉOó @ð  ‡�Ð4Ó5ÙLQÐRUËJÑchÐilÓcmÙ&+¨D£kÉÈtÐ`cÔIdñl˜sð l¨#ð l¸ð lÀcð lÐ]`ð lØ #ðlØ9AÀ#¹òló 6ðlð  ‡�Ð"Ó#Ù38¸³;Ñ\aÐbfÓ\gÙ16°tÈÔ1LÙ:@À»,ÑbhÐimÓbnñ)i˜gð )i¨Sð )iÈHÐUXÉMð )iØ!)¨#¡ð)ià*2°3©-ð)iàRZÐ[^ÑR_ò)ió $ð)ið\�cð ˜h s™mó ð
 ‡�ÐÓá! $›KÙ# D›kÙ$ T›{Ù" 4›[Ù�s˜sÔ#Ù˜˜a”.Ù#)¨$£<É6ÐRVË<ñ.Ø�S‰Mð.à�s‰mð.ð ˜‰}ð.ð �c‰]ð	.ð
 ð.ð ð.ð ˜C‘=ð.ð <DÀC¹=ò.ó  ð.ð$ ‡�Ð)Ó*á! $›KÙ# D›kÙ$ T›{Ù" 4›[Ù#)¨$£<É6ÐRVË<ñØ�S‰Mðà�s‰mðð ˜‰}ðð �c‰]ð	ð
 ˜C‘=ðð
 <DÀC¹=òó +ðð6;�Có ;ð ‡�Ð+Ó,Ù38¸³;Ñ_eÐfjÓ_kÙ7=¸d³|ñ 8¨C¡=ð ÈxÐX[É}ð Ø'/°¡}òó -ðð8 ‡�Ð&Ó'Ù.3°D«kÑZ`ÐaeÓZfÙ28¸³,ñ˜h s™mð È(ÐSVÉ-ð Ø"*¨3¡-òó (ðð8 ‡�Ð-Ó.Ù8>¸t»Ñ`fÐgkÓ`lñ #¨°©ð #ÐPXÐY\ÑP]ò #ó /ð#ðP ‡�Ð"Ó#Ù39¸$³<Ñ[aÐbfÓ[gñ  8¨C¡=ð È8ÐTWÉ=ò ó $ðð  ‡�Ð)Ó*Ù4:¸4³LÑ\bÐcgÓ\hñ  H¨S¡Mð ÈHÐUXÉMò ó +ðð& ‡�ÐÓÙ9?À»ÑagÐhlÓamñ X¨°#©ð XÐQYÐZ]ÑQ^ò Xó  ðXðD ‡�ÐÓ Ù06°t³ÑX^Ð_cÓXdñ ) ¨¡ð )ÈÐQTÉò )ó !ð)ð\ ‡�Ð%Ó&Ù6<¸T³lÑ^dÐeiÓ^jñ : h¨s¡mð :ÈhÐWZÉmò :ó 'ð:ð~ ‡�ˆMÓÙ˜S¨Q¸3Ô?ÉeÐTUÐZ[Ð`bÔNcñ 	H�ð 	HÈò 	Hó ð	Hô"˜	ô "ð ‡�Ð&Ó'Ù5;¸D³\Ñ]cÐdhÓ]iñ V X¨c¡]ð VÈXÐVYÉ]ò Vó (ðVð ‡�Ð&Ó'Ù`fÐgkÓ`lÙ6<¸T³lñ
eÐ-ð 
e¸ð 
eÐPXÐY\ÑP]ð 
eØ&.¨s¡mò
eó (ð
eð ‡�Ð#Ó$Ù28¸³,ÑZ`ÐaeÓZfñ ? (¨3¡-ð ?È(ÐSVÉ-ò ?ó %ð?ð ‡�Ð#Ó$ÙPVÐW[ÓP\Ù39¸$³<ñ˜ð ¨ð ÀÈÁð Ø#+¨C¡=òó %ðð$ $Ð ðk 8¨C¡=ð k°Tó kð ‡�Ð(Ó)Ù39¸$³<Ñ[aÐbfÓ[gñ 8 8¨C¡=ð 8È8ÐTWÉ=ò 8ó *ð8ð ‡�Ð'Ó(á$ TÐ3IÔJÙ" 4Ð1GÔHÙ$ TÐ3CÔDÙ" 4Ð1AÔBØ"&±U¸3À1ÈÔ5NÙ#)¨$£<É6ÐRVË<ñMØ˜‰}ðMà�c‰]ðMð ˜‰}ðMð �c‰]ð	Mð
 ˜3‘-ðMð
 03ðMð ˜C‘=ðMð <DÀC¹=òMó )ðMô.�Iô .ð ‡�Ð%Ó&ÙV\Ð]aÓVbÙ06°t³ñ2�mð 2¨gð 2ÀhÈsÁmð 2Ø (¨¡ò2ó 'ð2ð$ ‡�Ð0Ó1ÙU[Ð\`ÓUaÙ9?À»ñ
+˜sð 
+¨Wð 
+ÀXÈcÁ]ð 
+Ø)1°#©ò
+ó 2ð
+ð ‡�Ð&Ó'Ù7<¸R³yÑagÐhlÓamÙ6<¸T³lñ+˜wð +°ð +ÐQYÐZ]ÑQ^ð +Ø&.¨s¡mò+ó (ð+ð #ð ¨$ó ð ‡�Ð#Ó$Ù6<¸T³lÑ^dÐeiÓ^jñ 7 h¨s¡mð 7ÈhÐWZÉmò 7ó %ð7ð ‡�Ð$Ó%Ù05°b³	ÉEÐRWËLÙ.4°T«lÑV\Ð]aÓVbñA˜ð A¨#ð AÀTð AØ& s™mðAØFNÈsÁmòAó &ðAð8 ‡�ÐCÓDá;AÀ$»<ÑciÐjnÓcoñ Cð ¨sð ¸cð ÈGð Ø+3°C©=ðØS[Ð\_ÑS`òó Eðð ‡�Ð%Ó&Ù06°t³ÑX^Ð_cÓXdñ O ¨¡ð OÈÐQTÉò Oó 'ðOð ‡�Ð9Ó:ÙSYÐZ^ÓS_Ù39¸$³<ñ
3˜Sð 
3¨7ð 
3À8ÈCÁ=ð 
3Ø#+¨C¡=ò
3ó ;ñ
3rF   