U
    =el                     @   s   d dl Zd dlZd dlmZ d dlmZmZmZm	Z	m
Z
 d dlZd dlT d dlmZ d dlmZmZ ejZejZG dd dejZejej dS )	    N)	lru_cache)IteratorListNoReturnTupleUnion)*)cmf_calendar)MOrelativedeltac                   @   s   e Zd Zeeeee dddZedDeeeeedddZedEeeeee	e d	d
dZ
edFeeeee	e e	e dddZedGeeeeeeee	e dddZedHeeeeeeee	e dddZedeee	e f dddZed eedddZedId eeeedddZeedJeeeed!d"d#ZedKeed ejjf eeed$d%d&ZedLd eeeeef ed'd(d)Zed eeed*d+d,Z edMd eeed.d/d0Z!d1d2 Z"d3d4 Z#d5d6 Z$d7d8 Z%d9d: Z&d;d< Z'd=d> Z(ee)j*ddd?d@dA Z+edBdC Z,dS )NCmfCalendar)
date_startdate_endreturnc                 c   s0   t t||  jd D ]}| t| V  qdS )u   
        Генератор диапазона дат

        Args:
            date_start (Date): начальная дата
            date_end (Date): конечная дата

        Yields:
            Iterator[Date]: дата
           N)rangeintdaysdatetime	timedelta)r   r   i r   ./common/models/cmf_calendar.py_date_range   s    zCmfCalendar._date_rangeN)date	month_daypositionday_of_weekr   c           	         sz   |r2t  j jd }||k r$|} j|d}nDt  }| j j} fdd|D }|dk rn|| n|d }|S )u:  
        Вспомогательный метод для every_month и every_year
        Возвращает дату по указанному дню месяца или по позиции дня недели месяца

        Args:
            date (Date): дата
            month_day (int, optional): день месяца (1-31). Defaults to None.
            position (int, optional): позиция недели месяца. Defaults to None.
                                      (0-первая, 1-вторая, 2-третья, 3-четвертая, 4-последняя)
            day_of_week (int, optional): день недели. Defaults to None.
                                         (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

        Returns:
            Date: дата
        r   dayc                    s$   g | ]}| j  j kr| qS r   )month).0Zweekr   r   r   r   
<listcomp>9   s      z=CmfCalendar._get_day_by_position_in_month.<locals>.<listcomp>   )clZ
monthrangeyearr    replaceZCalendarZmonthdatescalendar)	r   r   r   r   Zdays_in_monthr   calendarweeksZday_of_weeksr   r"   r   _get_day_by_position_in_month   s    z)CmfCalendar._get_day_by_position_in_monthr   )
start_dateend_daterepeatsperiodr   c                 C   sR   g }|dk	rd}| }|rN|dkr*|d8 }n
||kr4qN| | |t|d7 }q|S )u  
        Возвращает список дат для типа повторения "Ежедневно"

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            period (int, optional): периодичность (каждый N день). Defaults to 1.

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: список дат
        NTr   r   )appendr   )r,   r-   r.   r/   r   current_dater   r   r   	every_day>   s    

zCmfCalendar.every_day)r,   r-   r.   r/   weekdaysr   c           	      C   s   g }|dk	rd}| t tdd }|r|D ]N}|t |d }|| krp|dkrV|d8 }n||krfd} qz|| |s* qzq*|t |d7 }q"|S )u]  
        Возвращает список дат для типа повторения "Еженедельно"

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            period (int, optional): периодичность (каждая N неделя). Defaults to 1.
            weekdays (List[int], optional): список дней недели. Defaults to None.
                                            (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: список дат
        NTr%   weekdayr   F)r*   )r   r
   r1   )	r,   r-   r.   r/   r4   r   r2   r6   r   r   r   r   
every_week`   s$    

zCmfCalendar.every_week)r,   r-   r.   r/   r   r   r   r   c                 C   s   g }|dk	rd}| }|rt j||||d}	|	| krX|dkrD|d8 }n
|	|krNq||	 |jd | }
|j|
d  }|
d d }
|j||
d}q|S )u  
        Возвращает список дат для типа повторения "Ежемесячно"
        Расчитывает даты в двух режимах:
            1 - по указанному дню, если установлен параметр month_day
            2 - по позиции дня недели, если установлены параметры position и day_of_week

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            period (int, optional): периодичность (каждый N месяц). Defaults to 1.
            month_day (int, optional): день месяца (1-31). Defaults to None.
            position (int, optional): позиция недели месяца. Defaults to None.
                                      (0-первая, 1-вторая, 2-третья, 3-четвертая, 4-последняя)
            day_of_week (int, optional): день недели. Defaults to None.
                                         (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: список дат
        NTr   r   r   r      )r'   r    )r   r+   r1   r    r'   r(   )r,   r-   r.   r/   r   r   r   r   r2   r   r    r'   r   r   r   every_month   s(     

zCmfCalendar.every_month)r,   r-   r.   r    r   r   r   r   c           
      C   sz   g }|dk	rd}| j |dd}|rvtj||||d}	|	| krb|dkrN|d8 }n
|	|krXqv||	 |j |jd d}q|S )u  
        Возвращает список дат для типа повторения "Ежегодно"
        Расчитывает даты в двух режимах:
            1 - по указанному дню, если установлены параметры month и month_day
            2 - по позиции дня недели месяца, если установлены параметры month, position и day_of_week

        Args:
            start_date (Date): начальная дата
            end_date (Date, optional): конечная дата. Defaults to None.
            repeats (int, optional): количество повторений. Defaults to None.
            month (int, optional): месяц (1-Янв, 2-Фев, ..., 12-Дек). Defaults to None.
            month_day (int, optional): день месяца (1-31). Defaults to None.
            position (int, optional): позиция недели месяца. Defaults to None.
                                      (0-первая, 1-вторая, 2-третья, 3-четвертая, 4-последняя)
            day_of_week (int, optional): день недели. Defaults to None.
                                         (0-ПН, 1-ВТ, 2-СР, 3-ЧТ ,4-ПТ ,5-СБ, 6-ВС)

            Обязателен один из двух аргументов, либо end_date, либо repeats

        Returns:
            List[Date]: _description_
        NTr   r    r   r8   )r'   )r(   r   r+   r1   r'   )
r,   r-   r.   r    r   r   r   r   r2   r   r   r   r   
every_year   s"     

zCmfCalendar.every_yearzmodels.CmfCalendarExclude)	exclusionr   c           	         sD  g } j j} jj} j} j} jdkrH jdkrHd}| |||}n jdkrpd}| j||| j j	d}n̈ jdkrd}| j||| j j
 jd	}n jd
krd}| j|||| j	d}nz jdkrd}| j|||| j
 jd}nP jdkr(d} fddtdD }| j|||||d}nd}| ||||}||fS )uw  
        Расчитывает и возвращает исключительные дни и приоритет исключения
        Приоритеты исключений (7-самый высокий, 1-самый низкий):
            7 - Ежедневно (каждый день)
            6 - Ежегодно (в указанный день)
            5 - Ежегодно (по позиции дня недели месяца)
            4 - Ежемесячно (в указанный день)
            3 - Ежемесячно (по позиции дня недели)
            2 - Еженедельно
            1 - Ежедневно (каждый N день)

        Args:
            exclusion (CmfCalendarExclude): исключение

        Returns:
            Tuple[int, List[Date]]: приоритет, список дат
        r3   r      Zevery_year_day   )r    r   Zevery_year_week_day   )r    r   r   Zevery_month_dayr$   )r   Zevery_month_week_day   )r   r   r7      c                    s"   g | ]}t  d | dr|qS )r   F)getattr)r!   r   r=   r   r   r#   '  s      z3CmfCalendar._calc_excluded_days.<locals>.<listcomp>)r4   )period_start_datevalueperiod_end_dater/   Zrepeat_timesZrepeat_typer3   r<   r    r   Zmonth_week_positionZmonth_day_weekr:   r   r7   )	clsr=   r   r,   r-   r/   r.   priorityr4   r   rD   r   _calc_excluded_days   sP    

 



 
zCmfCalendar._calc_excluded_days)r)   r   r   c                    s  d}g }|   d  }tjj|ddgdd|gdd|ggd	}d
}|D ]*}| |\}	}
||
krH|	|krH|	}|}qH|r|jj}|jj}|dkr|dd |j	D  ntj
j|ddgdd|gdd|ggd	}|rt|| djdkr|ddg |j}t|| dj}t|| dj}|dkrD| fdd|j	D  tjj||d}|stj||d} |_|j|_|j|_|j|_||_||_||_|  dS )u   
        Пересчет указанного дня календаря

        Args:
            calendar (CmfCalendar): объект календаря
            date (Date): дата

        Returns:
            NoReturn
        Nr   r   intervals.*rE   <=rG   >=parentfieldsfilterr   workc                 s   s*   | ]"}|j jd |jjd gV  qdS %H:%MN	from_timerF   strftimeto_timer!   intervalr   r   r   	<genexpr>S  s   z*CmfCalendar.recalc_date.<locals>.<genexpr>_typedefaultdefault_workweek.*default_workweek.intervals.*_intervals_total_minutesc                 3   s6   | ].}|j j kr|jjd |jjd gV  qdS rS   day_weekrF   rV   rW   rX   rY   r5   r   r   r[   e  s    rO   r   )r6   modelsCmfCalendarExcludelistrJ   exclude_typerF   intervals_total_minutesextend	intervalsCmfCalendarWorkWeekgetrC   load_fieldsdefault_workweekCmfCalendarDayrb   r'   r    r   day_typeinterval_total_minutesinterval_jsonsave)rH   r)   r   r   rj   day_num
exclusionsZexclusion_priorityr=   rI   Zexcluded_daysrp   rq   calendar_dayr   r5   r   recalc_date0  s^    



zCmfCalendar.recalc_dateT)r)   r   r   back_recalcr   c                 C   sh   |dkr$t j   }|jddd}|dkrF|tdd t jdd }| ||D ]}| || qRdS )u$  
        Пересчет дней календаря
        Если интервал не задан, делает пересчет от начала текущего года +2 года, итого 3 года

        Args:
            calendar (CmfCalendar): объект календаря
            date_start (Date, optional): начальная дата
            date_end (Date, optional): конечная дата
            back_recalc (bool, optional): пересчет назад только текущего года
        Nr   r;   rA   )Zyearsr0   )r   todayr   r(   r   r   r   rw   )rH   r)   r   r   rx   ry   r   r   r   r   recalc_calendar_dayu  s    zCmfCalendar.recalc_calendar_dayF)calendar_idr   detailr   c                 C   sH   t jjdd|gdd|ggdddgd}|s.d S |jj|jj|jji dS )	NZ	parent_id=r   rp   rq   rr   rQ   rP   )typerj   rh   r|   )rd   ro   rl   rp   rF   rr   rq   )rH   r{   r   r|   r   r   r   r   _get_day  s    zCmfCalendar._get_day)r)   r   r|   r   c                 C   s    t |d}| j|||d}|S )u<  
        Получает день календаря

        Args:
            calendar (Union[str, CmfCalendar, CmfType]): календарь
            date (Date): дата
            detail (bool, optional):

        Returns:
            day (dict): информация о дне календаря
        r   )r|   )ZcmfutilZget_obj_id_by_anyr   )rH   r)   r   r|   r{   r   r   r   r   get_day  s    zCmfCalendar.get_day)r)   r'   r   c              	      sN  |dkrt  j}nt|t js,t|t j r2|j}t t|dd}t t|dd}t }|}||kr| |j|j|j	ddg d||< |t j
dd7 }q`|dd	g |j}tjj|d
dgddd|gdd|ggdd|gdd|gggd}| D ]n\}	}
|	 d }t|| dj}t|| dj}||
d< ||
d< |dkrfdd|jD |
d< q|D ]}|jj}||jkrh| d }t|| dj}|dkr|t j
dd7 }qtt|| dj}||}|r||d< ||d< fdd|jD |d< |t j
dd7 }qtqhtjj|d
dgddd|gdd|ggdd|gdd|gggd} fdd|D }t|D ]n\}}}g }|jj}|jj}|dkrdd |jD }|D ].}	||	}|r||d< ||d< ||d< qqz| D ]V\}	}
tjj||	d }|stj||	d }|
 D ]\}}t||| q&|  qdS )!uQ  
        Расчитывает все дни года с учетом рабочих недель и исключений и сохраняет их в БД

        Args:
            calendar (CmfCalendar): объект календаря
            year (Union[str, int, Date, Datetime], optional): год. Defaults to None.
        Nr   r9      r   )rb   r'   r    r   rp   rq   rr   r0   r^   r_   r   rK   ORrE   rM   rL   rG   rN   r   r\   r`   rp   rq   rR   c                    s4   g | ],}|j j kr|jjd |jjd gqS rT   ra   rY   r5   r   r   r#     s    z-CmfCalendar.calc_one_year.<locals>.<listcomp>rr   r]   c                    s4   g | ],}|j j kr|jjd |jjd gqS r   ra   rY   r5   r   r   r#     s    c                    s   g | ]}  ||f qS r   )rJ   )r!   r=   rH   r   r   r#     s     c                 s   s*   | ]"}|j jd |jjd gV  qdS rS   rU   rY   r   r   r   r[     s   z,CmfCalendar.calc_one_year.<locals>.<genexpr>rc   )r   nowr'   
isinstancer   r   dictr6   r    r   r   rm   rn   rd   rk   rf   itemsrC   rF   rj   rE   rG   rl   re   sortedrg   rh   ro   setattrrs   )rH   r)   r'   Zstart_date_yearZend_date_yearZdays_of_yearr2   rn   Z	workweeksr   Zday_datart   rp   rq   ZworkweekZcurrent_period_dateZday_of_yearru   Zexcluded_dates_Zdatesr=   rj   rv   Z
field_namerF   r   )rH   r6   r   calc_one_year  s    

	








zCmfCalendar.calc_one_year)r)   from_dtto_dtr   c                 C   s  dd }d}t t jt|jpdd}| }| }||kr| ||}	|	d dkrl|t jdd7 }q8|	d	 D ]}
t j |
d d
 }t j |
d d
 }t j j|||d}t j j|||d}| t ddkr|t jdd7 }|||||}||7 }qt|t jdd7 }q8|S )u  
        Получает продолжительность рабочего времени в минутах из указанного диапазона дат

        Args:
            calendar (CmfCalendar): объект календаря
            from_dt (datetime): дата и время начала диапазона
            to_dt (datetime): дата и время конца диапазона

        Returns:
            minutes_sum (int): сумма минут
        c                 S   s8   t | |}t||}||k r0t||  d S dS d S )N<   r   )maxminr   total_seconds)r   r   interval_startinterval_endstartendr   r   r   	intersect=  s
    

z3CmfCalendar.get_duration_minutes.<locals>.intersectr   Zsecondsr   rR   r   r0   rj   rT   tzinfo)	r   timezoner   r   r   r   strptimetimecombine)rH   r)   r   r   r   Zminutes_sumtzr2   r-   r   rZ   rV   rX   r   r   Zinterr   r   r   get_duration_minutes0  s*    	

z CmfCalendar.get_duration_minutesr   )r)   r   durationr   c                 C   s  |s|S t t jt|jpdd}d}|dk r<d}t|}| }|dkrl| ||}|sf|d ntt|d }|D ]}	t j 	|	d d
 }
t j 	|	d d
 }t j j||
|d}t j j|||d}|
 t 
ddkr|t jdd	7 }|st||n|}|rt||n|}|||k r:t||  d
 nd8 }|dkrz qPqz|t j|s`dndd	7 }qD|r|t jt|d }n|t jt|d }|S )u$  
        Рассчитывает дату и время на основе длительности от указанной даты и времени

        Args:
            calendar (CmfCalendar): объект календаря
            from_dt (Datetime): дата и время от которой производится расчет
            duration (int, optional): длительность в минутах. Defaults to 0.

        Returns:
            to_dt (Datetime): рассчитанная дата и время
        r   r   FTrj   rT   r   r   r0   r   r%   )Zminutes)r   r   r   r   absr   r   rf   reversedr   r   r   r   r   r   )rH   r)   r   r   r   reverser2   r   rj   rZ   rV   rX   r   r   r   r   r   r   r   r   get_date_by_durationh  s8    
&z CmfCalendar.get_date_by_durationc                    s   t t fdd| jj}|S )Nc                    s   | j t  kS N)rb   strr6   )xr   r   r   <lambda>      z*CmfCalendar.get_schedule.<locals>.<lambda>)rf   rQ   rn   rj   )selfr   scheduler   r   r   get_schedule  s    zCmfCalendar.get_schedulec                 C   s   | j r
dS dS )NTF)ru   )r   r   r   r   r   is_exclude_day  s    zCmfCalendar.is_exclude_dayc                 C   s.   | j r*t| j d|  d}|dkr*dS dS )Nr   r\   ZweekendTF)rn   rC   r6   )r   r   rp   r   r   r   is_weekend_day  s
    zCmfCalendar.is_weekend_dayc           	      C   s   t j   j}| |}t ddd}t d}|D ],}||jjkrN|jj}||jjk r6|jj}q6t j j	|||d}t j j	|||d}||fS )N   ;   r   r   )
r   r   Z
astimezoner   r   r   rV   rF   rX   r   )	r   r2   r   r   Z_minZ_maxitemworkday_start_timeworkday_end_timer   r   r   set_worktime_border  s    


zCmfCalendar.set_worktime_borderc                 C   s  |  ddddddg |}d}| js(|S ||kr|jdddd}| |rX| |sR| |\}}||  krz|krn n||  kr|krn nq| | kr| | kr||kr||kr|t||  d	 t||  d	  7 }n||kr*||kr*|t||  d	 7 }n||krX||krX|t||  d	 7 }nj||  krp|krn n|t||  d	 7 }n4||  kr|krPn n|t||  d	 7 }q| | kr
| | kr
|d
t||  d	  7 }q| | kr||  kr4|krRn n|t||  d	 7 }nN||krv|t||  d	 7 }n*||krP|t|| ||   d	 7 }n| | kr||  kr|krn n|d
t||  d	  7 }nb||kr*|d
t||  d	  t||  d	  7 }n&||kr|d
t||  d	  7 }nH| | kr|d
t||  d	  7 }n|t||  d	 7 }|t	j
dd7 }|jdddd}q(|S )u~   
        Вычисляет величину паузы попадающее в нерабочие интервалы
        rn   default_workweek.intervals#default_workweek.intervals.day_weekru   $default_workweek.intervals.from_time"default_workweek.intervals.to_timer   )ZhourZminutesecondr   i  r   r0   )rm   rn   r(   r   r   r   r   r   r   r   r   )r   r   stopr2   Znon_working_timeZ	begin_dayr   r   r   r   r   calc_non_working_pause_duration  sx     
 
$
$ 




z+CmfCalendar.calc_non_working_pause_durationc                 C   s  |  ddddddg | js0t||  d S |}d}||kr| |rX| |s| |\}}||  krz|krn n4||  kr|krn n|t||  d 7 }qt||  d dkr4t||  d dk rnLt||  d dkr|t||  d 7 }n|t||  d 7 }nLt||  d dkrh|t||  d 7 }n|t||  d 7 }|tjd	d
7 }q8|S )u   
        Вычисляет величину паузы попадающее в рабочие интервалы
        чтобы правильно скорректировать elapsed_time
        rn   r   r   ru   r   r   r   r   r   r0   )	rm   rn   r   r   r   r   r   r   r   )r   pause_interval_start_timepause_interval_stop_timer2   pauser   r   r   r   r   calc_work_pause_duration  s@    	 
 
z$CmfCalendar.calc_work_pause_durationc              	   C   s^   t jjdddgdddgdddgdd| ggddd	d
dddgd}|D ]}|j  |  qBdS )u   
        Обработчик смотрит только активные циклы (не на триггерной паузе и не фуллтайм)

        Zbreachedr}   FalseZpausedZ	stop_timeZNULLzsla_goal.calendarZsla_goalcodeZwithin_calendar_hoursr   r   last_time_updater~   N)rd   ZCmfSDeskSlaCyclerf   r   Zset_nowrs   )r   Zcyclescycler   r   r   calendar_handlerH  s    
 
zCmfCalendar.calendar_handler)Z	only_onceZsystem_taskc                  O   s2   t jjddddddgd}|D ]}t| qd S )Nrn   r   r   ru   r   r   )rP   )rd   r   rf   r   )_args_kwargsZ	calendarsr)   r   r   r   celery_hourly_hookZ  s    zCmfCalendar.celery_hourly_hookc                 C   s   t | j d S r   )Zcmf_deferred_taskr   r   r   r   r   hourly_hook  s    zCmfCalendar.hourly_hook)NNN)NNr   )NNr   N)NNr   NNN)NNNNNN)NNT)F)F)N)r   )-__name__
__module____qualname__staticmethodDater   r   r   r+   r   r3   r7   r:   r<   classmethodr   rJ   r   rw   boolrz   r   r   r   r   r   ZcmfrP   ZCmfTyper   Datetimer   r   r   r   r   r   r   r   r   r   Z
celery_appZtaskr   r   r   r   r   r   r      s            !  
 *          3          /@D           }7   4T3-r   )r)   r&   r   	functoolsr   typingr   r   r   r   r   ZpytzZcmf.includeZcommon.fieldsr	   Zdateutil.relativedeltar
   r   r   r   r   r   ZAPPZHOOK_CRON_HOURLYr1   r   r   r   r   r   <module>   s$          