o
    jwm                     @  s  d dl mZ d dlZd dlZd dlmZ d dlmZmZ d dl	Z	d dl
mZ d dlmZmZmZmZmZmZ d dlmZ d dlmZ d d	lmZmZmZ d d
lmZmZmZ d dl m!Z!m"Z" d dl#m$Z$m%Z% d dl&m'Z' d dl(m)Z) d dl*m+Z+ d dl,m-Z-m.Z.m/Z/m0Z0 d dl1m2Z2m3Z3 d dl4m5Z5 d dl6m7Z7 d dl8m9Z9 d dl:m;Z;m<Z< e=e>Z?eddgdZ@dKd!d"ZAdLd%d&ZBdMd3d4ZCe@jDd5d6d7eeeefdNd@dAZEe@DdBedCeeeefdOdFdGZFe@DdHeeeefdPdIdJZGdS )Q    )annotationsN)	timedelta)	AnnotatedAny)parser)	APIRouterDependsFileHTTPExceptionRequest
UploadFile)text)Session)build_mcube_callback_urlbuild_mcube_refurl_for_callget_settings)get_db_clusterget_db_defaultsession_scope_cluster)AuthUserget_current_user)
has_column	has_table)CampaignCreateBody)campaign_core)audit_trail)check_business_time_windowcheck_remaining_minutes%compute_waiting_time_for_new_campaignfetch_executive_number)campaign_contacts_tablecampaigns_table)parse_csv_content)workbook_bytes_to_contact_rows)dispatch_campaign_to_queue)	format_dtnow_appz/api/campaigns	campaigns)prefixtagsdbr   business_idintrawr   return
int | Nonec                 C  s   |du rdS zt |W S  ty   Y nw t| }|r#| dkr%dS | d}t| |s1dS | td| d|||d }|rQ|d durQt |d S dS )zHMap UI/campaign bot label to ``{business_id}_bots.bot_id`` (cluster DB).Ndefault_botszSELECT bot_id FROM `zA` WHERE bot_name = :a OR agent_name = :b OR agent_id = :c LIMIT 1)abcr   )	r,   	Exceptionstrstriplowerr   executer   first)r*   r+   r-   labelbotsrow r>   L/var/www/html/livekitdocker/campaign_port/app/controllers/campaign_router.py_resolve_cluster_bot_pk_cluster&   s0   




r@   vboolc                 C  s&   t | tr| S t|   }|dv S )N)1trueonyes)
isinstancerB   r6   r7   r8   )rA   sr>   r>   r?   _php_filter_bool?   s   
rI   campaign_idcontact_ids	list[int]
agent_namer6   campaign_bot_raw
str | Nonecallback_urlrefurldelay_secondsfloatNonec                   s  t  }tt| }	d}
zt }t|t| |p|}
W d   n1 s%w   Y  W n ty6   d}
Y nw t|}|dk rAd}|dkrGd}tjt	dd4 I dH }t
|D ]\}}zt |}|td|	 dt|t|t| d  }|s	 W d   W qZt|d	pd
 }|s|td|	 dt t|t|t| d 	 W d   W qZd| d| }|td|	 d|t t|t|t| d W d   n1 sw   Y  |||t| d}t|jpd
 d}|
dur| dt|
 }n|dvrt| |d< |r!||d< nzt||d< W n
 ty3   Y nw |r<||d< nz	t|||d< W n
 tyO   Y nw |j||dI dH }i }z| }W n tyx   d|jppd
dd i}Y nw t|trt|dnd}d
}d
}t|trt|dp|dpd
 }t|dpd
 }t m}|r|td|	 d |p||pd!t t|t|t| d" nAt|tr|d#ndpt|tr|dndpd$|j }|td|	 d%t|dd& t t|t|t| d' W d   n	1 s$w   Y  W n tyD } ztd(|| W Y d}~nd}~ww |t|d) k rVt !|I dH  qZW d  I dH  dS 1 I dH siw   Y  dS )*aq  
    Fire outbound calls sequentially for the uploaded contacts.

    - Reads `number` from `{business_id}_campaign_contacts`
    - Calls MCube outbound URL (`mcube_local_outbound_url`, default `https://app3.syntheon.in/api/mcube/outbound-call`)
    - Sleeps `delay_seconds` between each trigger
    - Stores returned `mcube_call_sid` into `call_id` when available
    Ng        g      N@g      4@)timeoutz&SELECT id, number, name, status FROM `zF` WHERE id = :id AND campaign_id = :cid AND business_id = :bid LIMIT 1)idcidbidnumber UPDATE `ze` SET status = 'failed', updated_at = :u WHERE id = :id AND campaign_id = :cid AND business_id = :bid)urV   rW   rX   cmp_	_contact_zz` SET status = 'calling', call_id = :call_id, updated_at = :u WHERE id = :id AND campaign_id = :cid AND business_id = :bid)call_idr\   rV   rW   rX   )torM   r_   r+   /)NrZ   campaign_bot_idrP   rQ   )json_raw  okFmcube_call_sidcall_sidstatusz` SET status = 'initiated', call_id = :call_id, dialstatus = :ds, updated_at = :u WHERE id = :id AND campaign_id = :cid AND business_id = :bid	initiated)r_   dsr\   rV   rW   rX   errorhttp_zw` SET status = 'failed', dialstatus = :ds, updated_at = :u WHERE id = :id AND campaign_id = :cid AND business_id = :bid   )rk   r\   rV   rW   rX   z%auto-call failed contact_id=%s err=%s   )"r   r    r,   r   r@   r5   rS   httpxAsyncClientTimeout	enumerater9   r   mappingsr:   r6   getr7   r&   mcube_local_outbound_urlrstripr   r   postrc   rG   dictrB   status_codeloggerrl   lenasynciosleep)r+   rJ   rK   rM   rN   rP   rQ   rR   settingscontacts_tableresolved_bot_iddb0delay_sclienti
contact_idr*   r=   rY   refidpayloadoutbound_mcuberesp	resp_jsonrf   	mcube_sid
status_txtpreviewer>   r>   r?    _auto_call_contacts_after_uploadF   s  


(



$


&0r   rZ      )rz   requestr   bodyr   user.Annotated[AuthUser, Depends(get_current_user)]
db_default
db_clusterc                 C  s  t |j}|r|jstddddgidd|j}t|}t||s+tddd	d
dz|rP|jr8t|jnt	 }|j
d u rH|jt	 j
d}|tdd }	n
t	 }|tdd }	d}
|jrht|||j|j}
t |j}|rqdnd}|jpwd}t||||j}i d|d|jd|jd|rdndddd|pdd|
dt|dt|	d|dd|	dd|d|rdndd d!d"d#d$t	 d%t	 }t||d&r|j|d&< t||d'r|j|d'< t||d(r||d(< d)d*d+ | D }d)d,d+ | D }|td-| d.| d/| d0| t|td1 }|  |td2| d3d4|i  ! }|r{z"|td5||t|t|	t|tt	 tt	 d6 |  W n t"yz } zt#$d7| |%  W Y d }~nd }~ww |jp|j&pd8}t'||j(|d9d:|||r|)dnd d;| d<| j*r| j*j+nd d=tt	  ||r|)dnd |r|)dnd |d>d?
 |rt,|ni }||d@< dA|||dBW S  ty     t"y } zt#$dC| tddDt-|dEdFd|d }~ww )GN  Validation failed
start_timezrequired when scheduledmessageerrorsrz   detailre   z7Campaign table not found. Please contact administrator.zTable does not exist)r   rl   )tzinfoi  )daysrZ   ro   r   r+   namedescriptionri   	scheduledactivetotal_countwaiting_timedidend_time
start_datez%Y-%m-%dend_date
retry_timerun_now
batch_size
   batch_interval   
created_at
updated_atbot_idexeuctive_numberretry_logicz, c                 s  s    | ]	}d | d V  qdS )`Nr>   .0kr>   r>   r?   	<genexpr>9  s    z!store_campaign.<locals>.<genexpr>c                 s  s    | ]}d | V  qdS ):Nr>   r   r>   r>   r?   r   :  s    zINSERT INTO `z` (z
) VALUES ()zSELECT LAST_INSERT_ID()SELECT * FROM `"` WHERE campaign_id = :cid LIMIT 1rW   ag  
                        INSERT INTO scheduled_campaigns
                        (campaign_id, business_id, start_time, end_time, execute_at, delay_seconds, status, error_message, created_at, updated_at)
                        VALUES (:campaign_id, :business_id, :start_time, :end_time, :execute_at, 0, 'pending', NULL, :c_at, :u_at)
                        )rJ   r+   r   r   
execute_atc_atu_atz%scheduled_campaigns insert failed: %sUnknowncreate	Campaignsz!Campaign created successfully by z from z at )rJ   campaign_nameri   r   )
r+   user_id	user_nameactionmodule
table_nameresource_idresource_namemodifieddetailsrV   zCampaign created successfully)r   campaignrJ   rV   zCampaign creation error: %szFailed to create campaignF)r   rl   success).rI   r   r   r
   r+   r!   r   date_parserparser&   r   replacer   r   r   bot_namecallback_enabledr   r   custom_attempt_valuesr   r   r%   strftimer   joinkeysr9   r   r,   
scalar_onecommitrt   r:   r5   r{   rl   rollbackemailr   r   ru   r   hostry   r6   )r   r   r   r   r   r   r+   r   startendexecutive_numberr   r   r   waiting_time_valuesinsertcolsplaceholdersrJ   r   r   usernameoutr>   r>   r?   store_campaign   s   






	




$*r   z/{campaign_id}/contacts/upload.filer   c                   s  |j }t|}|jpd }|ds tddddgidd|td	| d
d| i 	 }	|	s<tdddid|
 I d H }
t|
dkrQtdddid|dr[t|
}n	t|
jddd}|dsutd|ddddzt|||t| |d }|  |dpg }|td| d
d| i	 }|rt|d pdnd}t|d }|| }|td| d|| d  |  |td	| d
d| i 	 }	d }|	rt|	d!pddkrt||}|d" std|d dd#|d$d||d%dt||}|d" s,td|d dd&|d'|d(|d)||d*d|	d+d,vrH|td| d-t | d. |  zt|	d/pQd0 pWd0}|	d/}d1}d }z!t }t|t||po|}W d    n	1 s|w   Y  W n ty   d }Y nw |jd2pd pd }|jd3pd pd }d4d5 |D }ttt|t| |||d urt|nd |||d6 d#d#d7t| d8|t|||d urt|nd d9}W n3 ty } zt !d:| dd;| d<}W Y d }~nd }~ww d#d=|	r|	d>nd? d#d#d@}|d||||d|| d#|dA	W S  ty9     tyX } zt !dB| tdCdD| ddd|d }~ww )ENrZ   )z.csvz.txt.xlsxr   r   r   zmust be csv, txt, or xlsxr   r   r   r   rW     r   Campaign not foundi   zFile too large (max 10MB)r   zutf-8r   )r   r     rl   F)r   r   contactszSELECT total_count FROM `r   r   r[   z0` SET total_count = :tc WHERE campaign_id = :cid)tcrW   r   validT
remain_min)r   r   insufficient_minutesr   contacts_importedr   BUSINESS_TIME_BLOCKEDbusiness_timecurrent_timecurrent_day)r   r   
error_coder   r   r   r   r   ri   )r   
processingA` SET status = 'active', updated_at = :u WHERE campaign_id = :cidr\   rW   r   r0   g       @zx-mcube-callback-urlzx-mcube-refurlc                 S  s(   g | ]}| d durt| d qS )rV   N)ru   r,   )r   r4   r>   r>   r?   
<listcomp>  s   ( z#upload_contacts.<locals>.<listcomp>)r+   rJ   rK   rM   rN   rP   rQ   rR   zCalling started for z contacts (2s delay).)r   startedr   rM   r+   r   rN   zauto-call start failed: %szFailed to start calling: )r   rl   zCampaign scheduled for r   zN/A)r   r   r   cron_scheduled)	r   r   r   previous_countcontacts_datainserted_contactsrJ   r   call_resultzupload error: %sre   zFailed to upload contacts: )"r+   r!   filenamer8   endswithr
   r9   r   rt   r:   readr|   r#   r"   decoderu   r   store_campaign_contactsr,   r   r   r   r&   r6   r7   r   r@   r5   headersr}   create_taskr   r{   rl   )rJ   r   r   r   r   r   r+   r   fnamer   r-   parse_resultstore_resultr  currentcurrent_total	new_countupdated_totalr  minutes_checkbtcrM   rN   rR   resolved_for_uidbxrP   rQ   rK   r   r>   r>   r?   upload_contacts  s>  	











	r  z/{campaign_id}/startc                 C  s\  |j }t|}t||stdddid|td| dd| i  }|s0tdddidt||}|d sQtd	|d d
d|	d|	d|	dddt
||}|d sktd	|d d
d|	dddd|	ddvrtd	d|	d d
|	dddt|}	d}
t||	rt|td|	 dd| i }
|
dkr|	ddkr|td| dt | d |  ddlm} ||t|  dddd S |td| d!t | d |  z t|t| |d
d
t j |	ddkrd"nd#}|ddd$d%W S  ty- } ztd&| td'd(d
t|d)| d*d+d|d }~ww ),Nr   r   r   r   r   r   rW   r   r   Fr   r   r   r   )r   r   r   r   r   r   Tr   r   )r   r   r   r   ri   )r   r   pausedzXCampaign can only be started from active, processing, or paused status. Current status: )r   r   current_statuszSELECT COUNT(*) FROM `zE` WHERE campaign_id = :cid AND (status = 'pending' OR status IS NULL)r  r[   zD` SET status = 'completed', updated_at = :u WHERE campaign_id = :cidr   )delete_queue_for_campaignz6Campaign has no pending contacts. Marked as completed.)r   r   	completedr   zCampaign resumed successfullyzCampaign started successfullyzCampaign execution has been queued and will start processing in the background. You can continue using the application while calls are being made.)r   r   queuedmessage_detailzdispatch error: %sre   zFFailed to queue campaign job. Please ensure queue workers are running.zError: zH. Please check queue configuration and ensure queue workers are running.)r   r   rl   r  )r+   r!   r   r
   r9   r   rt   r:   r   ru   r   r    r,   r   r&   r   app.utils.rabbitmq_campaignr  r$   r   queue_first_campaignr5   r{   rl   r6   )rJ   r   r   r   r+   r   r   r  r  r   pendingr  msgr   r>   r>   r?   start_campaign7  s   




	





r!  )r*   r   r+   r,   r-   r   r.   r/   )rA   r   r.   rB   )r+   r,   rJ   r,   rK   rL   rM   r6   rN   rO   rP   rO   rQ   rO   rR   rS   r.   rT   )
r   r   r   r   r   r   r   r   r   r   )rJ   r6   r   r   r   r   r   r   r   r   r   r   )rJ   r6   r   r   r   r   r   r   )H
__future__r   r}   loggingdatetimer   typingr   r   rp   dateutilr   r   fastapir   r   r	   r
   r   r   
sqlalchemyr   sqlalchemy.ormr   
app.configr   r   r   app.databaser   r   r   app.dependenciesr   r   app.repositories.schema_helperr   r   app.schemas.campaignr   app.servicesr   app.services.audit_logr   app.services.campaign_corer   r   r   r   app.utils.cluster_table_namesr    r!   app.utils.csv_parser"   app.utils.excel_parser#   app.utils.queue_dispatchr$   app.utils.timezone_utilr%   r&   	getLogger__name__r{   routerr@   rI   r   rx   r   r  r!  r>   r>   r>   r?   <module>   sX     



 (  3