U
    h                    @   s   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
mZmZmZmZ d dlZd dlZd dlT d dlmZ d dlmZmZ dd	d
ddddddddddZejZejZejZG dd dejZdS )    N)	lru_cache)deque
namedtuple)IteratorListNoReturnTupleUnion)*)cmf_calendar)MOrelativedeltau   янвu   февu   марu   апрu   маяu   июнu   июлu   авгu   сенu   октu   нояu   дек)                        	   
         c                       s  e Zd Zejjd Zejjddg Zeeee	e dddZ
ed`eeeeedd	d
Zedaeeeeee dddZedbeeeeee ee dddZedceeeeeeeee dddZeddeeeeeeeee dddZedeeeeeef dddZedfe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dgd eeeed%d&d'Zed(d)Zeee d e!j"j#f eed!d*d+Z$ed,d- Z%ee&d.d/d0 Z'i Z(i Z)ee*d1d2e eed3d4d5Z+edhd ee eeef ed6d7d8Z,dieeed:d;d<Z-eeed=d>d?Z.edjd eeed@dAdBZ/edkd eeedDdEdFZ0edGdH Z1edIdJ Z2ee3d$d$dKdLdMdNdO Z4dPdQ Z5dRdS Z6dTdU Z7dVdW Z8 fdXdYZ9 fdZd[Z:d\d] Z; fd^d_Z<  Z=S )lCmfCalendar)DayDatarecalc_calendarload_holidays)
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]: дата
        r   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>K   s      z=CmfCalendar._get_day_by_position_in_month.<locals>.<listcomp>r   )clZ
monthrangeyearr/   replaceZCalendarZmonthdatescalendar)	r*   r+   r,   r-   Zdays_in_monthr.   calendarweeksZday_of_weeksr'   r1   r(   _get_day_by_position_in_month1   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_dayP   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]: список дат
        NTr3   weekdayr   F)r8   )r   r   r?   )	r:   r;   r<   r=   rB   r#   r@   rD   r.   r'   r'   r(   
every_weekr   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   )r5   r/   )r   r9   r?   r/   r5   r6   )r:   r;   r<   r=   r+   r,   r-   r#   r@   r.   r/   r5   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.   rF   )r5   )r6   r   r9   r?   r5   )
r:   r;   r<   r/   r+   r,   r-   r#   r@   r.   r'   r'   r(   
every_year   s"     

zCmfCalendar.every_year)	from_timeto_timer    c                 C   sf   t j }t j || } t j ||}t dd| krJ|t jdd7 }| |kr^tddd | |fS )u  
        Нормализует интервал времени и проверяет его на корректность.
        Так как у datetime нет времени 24:00,
        то время окончания 00:00 конвертируется в 00:00 следующего дня,
        как будто это интервал, например, 18:00 - 24:00

        Args:
            from_time (Time, optional): время начала интервала. Defaults to None.
            to_time (Time, optional): время окончания интервала. Defaults to None.

        Returns:
            Tuple[Datetime, Datetime]: дата и время начала, дата и время окончания
        r      Zhoursuo   Время начала должно быть раньше времени окончания интервалаTabort)r$   r*   todaycombinetimer%   	cmf_alert)rJ   rK   rP   r'   r'   r(   normalize_time_interval  s    
z#CmfCalendar.normalize_time_intervalc                 C   s.   | r|sdS t j| |\} }||   d S )u?  
        Вычисляет в минутах интервал времени

        Args:
            from_time (Time, optional): время начала. Defaults to None.
            to_time (Time, optional): время окончания. Defaults to None.

        Returns:
            int: минуты
        r   <   )modelsr   rT   total_seconds)rJ   rK   r'   r'   r(   get_interval_minutes  s    z CmfCalendar.get_interval_minuteszmodels.CmfCalendarExclude)	exclusionr    c           	         sb  g }d} j j} jj} j} j} jdkrN jdkrNd}| |||}n jdkrvd}| j||| j j	d}n jdkrd	}| j||| j j
 jd
}n jdkrd}| j|||| j	d}n jdkrd}| j|||| j
 jd}nh jdkr.d} fddtdD }| j|||||d}n, jdkrZ jdkrZd}| ||||}||fS )uw  
        Расчитывает и возвращает исключительные дни и приоритет исключения
        Приоритеты исключений (7-самый высокий, 1-самый низкий):
            7 - Ежедневно (каждый день)
            6 - Ежегодно (в указанный день)
            5 - Ежегодно (по позиции дня недели месяца)
            4 - Ежемесячно (в указанный день)
            3 - Ежемесячно (по позиции дня недели)
            2 - Еженедельно
            1 - Ежедневно (каждый N день)

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

        Returns:
            Tuple[int, List[Date]]: приоритет, список дат
        NrA   r   r   Zevery_year_dayr   )r/   r+   Zevery_year_week_dayr   )r/   r,   r-   Zevery_month_dayr   )r+   Zevery_month_week_dayr   )r,   r-   rE   r   c                    s"   g | ]}t  d | dr|qS )r-   F)getattr)r0   r&   rY   r'   r(   r2   i  s      z2CmfCalendar.calc_excluded_days.<locals>.<listcomp>)rB   )period_start_datevalueperiod_end_dater=   Zrepeat_timesrepeat_typerA   rI   r/   r+   month_week_positionmonth_day_weekrG   r!   rE   )	clsrY   r#   priorityr:   r;   r=   r<   rB   r'   r[   r(   calc_excluded_days0  sT    

 



 
zCmfCalendar.calc_excluded_days)r7   r*   r    c                    s  d}g }|   d  }tjj|ddgdddgdd|gd	d
|ggd}d}|D ]4}| |\}	}
|	dkrlqP||
krP|	|krP|	}|}qP|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rX| fdd|j	D  tjj||d}|stj||d} |_|j|_|j|_|j|_||_||_||_|  |S )u   
        Пересчет указанного дня календаря

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

        Returns:
            NoReturn
        Nr.   r
   intervals.*r_   !=r\   <=r^   >=parentfieldsfilterr   workc                 s   s*   | ]"}|j jd |jjd gV  qdS %H:%MNrJ   r]   strftimerK   r0   intervalr'   r'   r(   	<genexpr>  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 rn   day_weekr]   rJ   rq   rK   rr   rC   r'   r(   rt     s    rj   r*   )rD   rV   CmfCalendarExcludelistrd   exclude_typer]   intervals_total_minutesextend	intervalsCmfCalendarWorkWeekgetrZ   load_fieldsdefault_workweekCmfCalendarDayr{   r5   r/   r.   day_typeinterval_total_minutesinterval_jsonsave)rb   r7   r*   r.   r   day_num
exclusionsZexclusion_priorityrY   rc   Zexcluded_daysr   r   calendar_dayr'   rC   r(   recalc_dater  sj    




zCmfCalendar.recalc_dateT)r7   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   rH   r   )yearsr>   )r$   rP   r*   r6   r   r%   r)   r   )rb   r7   r   r   r   rP   r*   r'   r'   r(   recalc_calendar_day  s    zCmfCalendar.recalc_calendar_dayr   z&type,intervals,intervals_total_minutesc                 C   s   t t|d}| ||S )u  
        Получает день календаря

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

        Returns:
            day (dict): информация о дне календаря
        r   )sysinterncmfutilZget_obj_id_by_any_get_day)rb   r7   r*   calendar_idr'   r'   r(   get_day  s    zCmfCalendar.get_dayc                  K   s   t dd  tj  d S )NCmfCalendar:day_cache_clear)Zcmf_emit_server_eventrV   r   _day_cache_clear_kwargsr'   r'   r(   day_cache_clear  s    
zCmfCalendar.day_cache_clearr   c                  K   s(   t jj  t jj  t jj  d S )N)rV   r   r   cache_clear_day_cache_int_internclear_day_cache_data_internr   r'   r'   r(   r     s    zCmfCalendar._day_cache_cleari  )maxsize)r   r*   r    c                 C   s   t jjdd| gdd|ggdddgd}|sXtj| dgd	}td
|dd|j ddd t jt	
|jtdd |jD t jj|j|j}t jj||S )NZ	parent_id=r*   r   r   r   rl   rk   dirty)rk   u	   День z%d.%m.%Yu*    не найден в календаре "uO   ". Попробуйте запустить пересчет календаря.TrN   c                 s   s*   | ]"}t |d  t |d fV  qdS )r   r   N)r   r   rr   r'   r'   r(   rt     s   z'CmfCalendar._get_day.<locals>.<genexpr>)rV   r   sgetr   Zget_obj_by_idrS   namer   r   r   r   r   tupler   r   
setdefaultr   r   )r   r*   r.   r7   ddr'   r'   r(   r     s"    
	zCmfCalendar._get_day)r7   r5   r    c                    s   |dkrt j  j}nt|t js.t|t j r4|j}t t|dd}t t|dd}t }|}||kr| |j|j|j	ddg d||< |t j
dd7 }qb|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dd|gdd|gggd}| D ]r\}	}
|	 d }t|| dj}t|| dj}||
d< ||
d< |dkrfdd|jD |
d< q|D ]}|jj}||jkrd}g }| d }t|| dj}|dkr|t j
dd7 }q|dkrt|| dj}fdd|jD }||}|r:||d< ||d< ||d< |t j
dd7 }qqtjj|d
dgdddgddd|gdd|ggdd|gdd|ggdd|gdd|ggggd} fdd|D }t|D ]r\}}}d}g }|jj}|dkr |jj}d d |jD }|D ].}	||	}|r||d< ||d< ||d< qqtjj||d
gd!}|D ]`}||jjd}
|
rP|
dg   |
 D ]\}}t||| q|jrP|j d"d# qP| D ]@\}	}
tj||	d$}|
 D ]\}}t||| q|   qdS )%uQ  
        Расчитывает все дни года с учетом рабочих недель и исключений и сохраняет их в БД

        Args:
            calendar (CmfCalendar): объект календаря
            year (Union[str, int, Date, Datetime], optional): год. Defaults to None.
        Nr   r      r   )r{   r5   r/   r.   r   r   r   r>   rw   rx   r
   re   ORr\   rh   rg   r^   ri   r.   ru   ry   r   r   rm   c                    s4   g | ],}|j j kr|jjd |jjd gqS ro   rz   rr   rC   r'   r(   r2   U  s    z-CmfCalendar.calc_one_year.<locals>.<listcomp>r   rv   c                    s4   g | ],}|j j kr|jjd |jjd gqS r   rz   rr   rC   r'   r(   r2   k  s    r_   rf   c                    s"   g | ]}|j r ||f qS r'   )r_   rd   )r0   rY   )rb   r'   r(   r2     s   c                 S   s(   g | ] }|j jd |jjd gqS r   rp   rr   r'   r'   r(   r2     s   )rj   r5   rk   TZ	only_datar|   )!r$   nowr5   
isinstancer*   r"   dictrD   r/   r.   r%   r   r   rV   r   r~   itemsrZ   r]   r   r\   r^   r   r}   sortedr   r   r   popsortsetattr
is_changedr   )rb   r7   r5   Zstart_date_yearZend_date_yearZdays_of_yearr@   r   	workweeksr*   Zday_datar   r   r   workweekZcurrent_period_dater   Zday_of_yearr   Zexcluded_dates_datesrY   Zcalendar_daysr   Z
field_namer]   r'   )rb   rD   r(   calc_one_year  s    

	










zCmfCalendar.calc_one_yearF)
back_yearsforcer    c                 C   s   |  dddg |dkr"t| j}|dk s2|dkr>tddd	 | jsL|sLdS tj j}|rd|| n|}|t| j }d}t	||D ]<}|st
t|d
d
}tj| | t
t|dd| _q|| _|   d| _| jdd dS )u  
        Пересчитывает дни календаря.
        Пересчет начинается от текущего года вперед на количество лет,
        указанных в параметре calc_num_years календаря.
        Если в параметре back_years указано положительное число, то пересчет начнется с прошлых лет.
        Например:
            back_years = 3
            calc_num_years = 5
            текущий год - 2023
            Будут пересчитаны года с 2020 по 2027 включительно

        Args:
            force (bool, optional): принудительный пересчет дней календаря. Defaults to False.
            back_years (int, optional): количество прошлых лет. Defaults to 0.
        r   calc_num_yearscalc_num_back_yearsNr   r   u@   Параметр back_years должен быть от 0 до 10TrN   r   r   r   Fr   )r   r"   r   rS   r   r$   r   r5   r   r!   r*   rV   r   r   
calc_until	calc_fromr   r   )selfr   r   current_yearZ	from_yearZto_yearr   r5   r'   r'   r(   r     s(    

zCmfCalendar.recalc_calendar)from_dtto_dtr    c                 C   sX  d}t t jt| jpdd}| }| }tjjdd| gdddgdd|gdd	|ggdd
dgd}|D ]}|j|kr|j|kr||jd 7 }qj|j	D ]}	t j 
|	d d }
t j 
|	d d }t j j|j|
|d}t j j|j||d}| t ddkr|t jdd7 }t||}t||}||k rDt||  nd}||7 }qqj|S )u  
        Получает дельту рабочего времени в секундах между начальной и конечной датой и временем

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

        Returns:
            work_timedelta (int): дельта рабочего времени в секундах
        r   Zsecondsrj   r   r   rm   r*   rh   rg   r   r   r   rU   ro   r   tzinfor>   )r$   timezoner%   r"   r*   rV   r   Zslistr   r   strptimerR   rQ   maxminrW   )r   r   r   Zwork_timedeltatzr:   r;   	work_dayswork_dayrs   rJ   rK   interval_startinterval_endstartendZtime_periodr'   r'   r(   get_work_timedelta  s8     	


zCmfCalendar.get_work_timedelta)r7   r   r   r    c                 C   s  dd }d}t t jt|jpdd}| }| }	||	kr| ||}
|oh|| kph|| k}|
jdkr|s|t jdd7 }q8|r|}|
}|jdkr|| kr|t jdd7 }n|| kr|t jdd8 }| ||}q|}
|| kr$|| kr$||
j7 }|t jdd7 }q8|
j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 }q*|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 )NrU   r   )r   r   r"   rW   )r   r   r   r   r   r   r'   r'   r(   	intersect  s
    

z3CmfCalendar.get_duration_minutes.<locals>.intersectr   r   rm   r   r>   ro   r   )r$   r   r%   r"   r*   r   typer   r   r   rR   rQ   )rb   r7   r   r   force_include_endsr   Zminutes_sumr   r@   r;   r.   Zforce_includeZcur_dateZcur_dayrs   rJ   rK   r   r   Zinterr'   r'   r(   get_duration_minutes  sH    	



z CmfCalendar.get_duration_minutesr   )r7   r   durationr    c                 C   s  |s|S t t jt|jpdd}d}|dk r<d}t|}| }|dkrf| ||}|sd|jntt	|j}|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 }|st||n|}|rt||n|}|||k r4t||  d	 nd8 }|dkrv qJqv|t j|sZd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   FTro   r   r   r>   rU   r3   )Zminutes)r$   r   r%   r"   absr*   r   r   r~   reversedr   rR   rQ   r   r   rW   )rb   r7   r   r   r   reverser@   r.   r   rs   rJ   rK   r   r   r   r   r   r'   r'   r(   get_date_by_durationU  s8    
&z CmfCalendar.get_date_by_durationc                 C   sT  t jj| | d}ttjt| jp&dd}|j}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ddkr dS |
 tddkrtjj|
tjj |d}
||	kr||
k r dS ||	kr, dS ||
krF|dkrF dS |d8 }qFdS )	u=   
        Вычисляет флаг calendar_paused
        r7   r*   r   r   r   ro   r   FT)rV   r   r   r*   r$   r   r%   r"   r   lenr   rR   rQ   r   )r7   
check_dater.   r   r   r&   rs   rJ   rK   r   r   r'   r'   r(   get_sla_cycle_calendar_paused  s,    
(



z)CmfCalendar.get_sla_cycle_calendar_pausedc                 C   s  t jj| | d}ttjt| jp&dd}|j}d}t	|d }|D ]6}tj
|d d }tj
|d d }	tjj| ||d}
tjj| |	|d}|
 tddkr| tddkr|tjdd7 }|jdddd	}|} q| tddkr*tjj|tjj |d}||
krJ||k rJ|} qn0||
kr`|
} qn||krz|dkrz q|d8 }qJtjj|tjj |d}|tjdd
7 }|stj| |d}|S )uX   
        Вычисляет next_time_update - сл дата пересчета
        r   r   r   Nr   ro   r   rM   )ZminutesecondZmicrosecondr>   r7   r   )rV   r   r   r*   r$   r   r%   r"   r   r   r   rR   rQ   r6   r   r   get_sla_cycle_next_time_update)r7   r   r.   r   r   resultr&   rs   rJ   rK   r   r   r'   r'   r(   r     s@    
(



z*CmfCalendar.get_sla_cycle_next_time_updatez	@minutelyr   )Z	only_onceZ
system_jobZschedulerc   c               	   C   s   t j  } dd| gdddgdddgg}dd	d
ddddddg	}d\}}tjj||||| gd}|sdq||7 }|D ]F}|j|_tj|j	j
|jjd|_tj|j	j
|jjd|j_|  qpqDd S )Nnext_time_updaterg   Z	stop_timer   ZNULLcurrentTsla_goalzsla_goal.calendarzsla_goal.calendar.timezonecodeZpause_interval_stop_timeZpause_interval_start_timelast_time_updatecalendar_paused)r   d   )rl   rk   slicer   )r$   r   rV   ZCmfSDeskSlaCycler~   r   r   r   r   r   r7   r]   r   r   r   )r   rl   rk   r   limitZcyclescycler'   r'   r(   celery_minutely_hook  s&    
 z CmfCalendar.celery_minutely_hookc           &         s  ddl m} ddlm} ddd}tj j}||d f}|D ](}|tjdd	t	|
d
}| shq>t|ddd}t| }	W 5 Q R X |	d}
||
krtd|
 d| d|  q>g }dd |	dD }|	d}|	d}|D ].}|d tj|
 d  d }|j dt|j  d|j }tjjdd| gdddgdddgd d!d|gd"d|gggd#d$}|r~q|d%}|d&}|r||}n||}d'}tt fd(d)|d'}tt fd*d)|d'}|r|d+ }n|r |d, }|rFtj|
 d| d }|d-|j dt|j  d|j 7 }d.|d/d0| j }d|| d1| d2|||j|j| |jd d3 d4d5
}tjjd6d7d8| gd9gd4d:}|stj| d;}| D ]\}}t ||| q|j!rd#|_!|j"r|j#d4d< |dkr|$d=g | $d>g | j%}|r>| }n| }|j&} tj'j(||d9gd?}!|| |!}"|"D ]\\}#}$|$r|#stj)|d;}#|$j*|#_*|$j+|#_+|$j,|#_,n|#rd4|#_!|#j"rn|#j#d4d< qnn,|$d@g |j&D ]}#d4|#_!|#j#d4d< q|-  |.  |j#d4d< |/| qtjj(d6dA|gd6d7d.| dBgdCdd4ggdD}%|%D ]}|j0d4dE qTq>| $dFg | 1  | j#d4d< d'S )Gu  
        Метод для кнопки "Загрузить праздники".
        Добавляет в исключения календаря выходные/празничные дни из файла в формате JSON.
        Исключения добавляются на текущий и следующий год (при наличии соответствующих файлов).
            - файлы с данными находятся в каталоге /opt/eva-app/cmf/contrib/calendar/
            - имя файла должено содержать только год (например, 2024.json)
        Добавления не происходит, если пользователь уже добавил исключение на такую же дату.
        При повторном выполнении метода происходит перезагрузка/восстановление/удаление,
        если поменялись данные в файле.
        r   )zip_longest)Pathu   Выходной деньu   Рабочий день)r   r   r   Zcontribr7   z.jsonrzutf-8)encodingr5   u   Год (uL   ) указанный в файле не соответствует году u    в имени файла c                 S   s   i | ]}|d  |d qS )idr   r'   )r0   holidayr'   r'   r(   
<dictcomp>2  s      z-CmfCalendar.load_holidays.<locals>.<dictcomp>holidays	transfersr   r*   .z%Y.%m.%d rj   r   r_   rA   r=   r   r\   r^   F)rl   is_autor   r   Nc                    s   |  d kS )Nfromr   itemZdate_r'   r(   <lambda>T      z+CmfCalendar.load_holidays.<locals>.<lambda>c                    s   |  d kS )Ntor   r   r  r'   r(   r  U  r  r  r   u    за Zexclude_z%Y_%m_%d:z ()r   T)
r_   r   r   r\   r^   r/   r+   ra   r`   r   r   ZLIKE%r
   )rl   rk   Zinclude_deleted)rj   r   re   r   )rj   r{   rk   r   zNOT INz_%r   )rl   )r   r   )2	itertoolsr   pathlibr   r$   r   r5   ZconfigZ
CMF_FOLDERstrwith_suffixexistsopenZjsonloadsreadr   ZloggingZwarningr   r*   r.   MONTHS_SHORTr/   rV   r}   r   nextrl   r   rD   r   r   Zcmf_deletedr   r   r   r   r   ZCmfCalendarWorkWeekIntervalr~   ZCmfCalendarExcludeIntervalrJ   rK   Zinterval_minutesZ_calc_intervals_total_minutesZ_calc_exclude_typer?   delete_set_as_dirty)&r   r   r   Z	day_typesr   r   r5   Z	file_pathfZcalendar_dataZcalendar_yearZexclude_codesr   r   r   Z	date_datar*   Zdate_strZexcluder   r   r   ZtransferZtransfer_fromZtransfer_toZtransfer_dater   Zexclude_dataZfieldr]   r   rD   Zexclude_intervalsZworkweek_intervalsr   Zexclude_intervalZworkweek_intervalZexcludesr'   r  r(   r     s    





  



 $




zCmfCalendar.load_holidaysc                 C   s
   d| _ dS )u   
        Помечает, что календарь необходимо пересчитать и сбрасывает кэш дней
        TN)r   r   r'   r'   r(   r    s    zCmfCalendar._set_as_dirtyc                 C   s   | j jsdS | j stddd d| j d}tjjdd| jgd	d
dggd	gd}|rxd|_ |jdd d|j d| }t| dS )uX   
        Изменяет флаг "Календарь по умолчанию"
        Nu   Для сброса флага "Календарь по умолчанию" вы должны установить его у любого другого календаря.TrN   uf   Во всех новых проектах будет использоваться календарь ""r   rf   
is_defaultr   r   Fr   u^   Сбросили флаг "Календарь по умолчанию" у календаря "z". )	r  r   rS   r   rV   r   r   r   r   )r   msgZcurrent_default_calendarr'   r'   r(   _set_is_default  s$    
zCmfCalendar._set_is_defaultc                 C   s.   | j jrd| _ | jjrd| _| jjr*d| _dS )u}   
        Исправляет нулевые значения, если приходит null выставляет 0
        r   N)r   Zis_nullr   r   r  r'   r'   r(   fix_null_value  s    zCmfCalendar.fix_null_valuec                    s   t   dddg S )Nr   r   r   )supersave_preload_fieldsr  	__class__r'   r(   r    s    zCmfCalendar.save_preload_fieldsc                    s(  | j r~t| dd rtjj| jdddgd}|j| _|j| _|j| _|j	D ]h}|
 }| |_d|_|jdd |d	g |jD ]}|
 }||_|jdd q|jsL|jsL|| _qL|jD ]P}|
 }	| |	_|	jdd |d	g |jD ]}|
 }|	|_|jdd qqnntj  j}
ttj|
  | _tj| d
d}tdD ]}t|d| dd qP|jdd || _|   |   t| jdkrt ddd t| jdk rt ddd t| jdkrt ddd t| jdk rt ddd | jj!s| jj!r| "  t# j||S )Ntemplater
   zworkweeks.*zexclusions.*)r   rk   FTr   re   u   По умолчаниюrj   r   r   r.   ru   Zweekend   u\   Количество лет расчета не должно быть больше 30 летrN   r   u]   Количество лет расчета не должно быть меньше 1 годаr   un   Количество лет расчета в прошлое не должно быть больше 10 летr   uf   Количество лет расчета в прошлое не должно быть меньше 0)$Zis_newrZ   rV   r   r   r   r   r   r   r   Zclonerj   systemr   r   r   r\   r^   r   r   r$   r   Z
astimezoner   r"   Z	utcoffsetrW   r   r!   r   r  r  rS   r   r  r  )r   argskwargsZtemplate_calendarZtemplate_workweekZnew_workweekZtemplate_intervalZnew_intervalZtemplate_exclusionZnew_exclusionr   r   r&   r  r'   r(   r     sd    



zCmfCalendar.savec              	   c   s  dd }d}|}t g }d }d}	d}
|sddd|gdd	d
ggg}|rp|	sX|dd	|g |rp|
sp|dd	|g |tjjdd	| g|gdddddgdgddgd |s| j }d }|sd| j d}nd| j d| j  }|S |	 }|j
|kr"|jd
kr||}|j|_|j|_d}	|j
|krn|jd
krj|rR|j|_|j|_n||}|j|_|j|_d}
|jd
kr~|}|V  t|j
jtj tjdd 
 }|d7 }q$d S )Nc                 S   s0   t t| D ]}| | jdkr| |   S qd S )Nrm   )r!   r   r   )bufferr&   r'   r'   r(   _get_next_work_day4  s    z1CmfCalendar.work_days.<locals>._get_next_work_dayr   Fr   r*   rh   r   z==rm   rj   r   r   zparent.timezoner   )rl   rk   Zorder_byr   u   Календарь "u   " не рассчитан.u   " рассчитан до Tr   r>   )r   r?   r   rV   r   r~   r   loadr   popleftr*   r   r   r   DatetimerQ   r]   r   rR   r$   r%   )r   r:   r   Zfinish_dater'  nZ	next_dater&  Zprev_work_dayZstart_date_givenZfinish_date_givenZdate_filterr   messager   Znext_work_dayr'   r'   r(   r   2  sd    	

&zCmfCalendar.work_daysc                    s   |  dg | jrtddd tjj| dgd}|rZddd	 |D }td
| dd tjj| d}|rtd| ddd tj	j| ddgd}|rddd	 |D }td| dd t
 j||S )Nr  u   Нельзя удалить календарь по умолчанию. Сначала назначьте любой другой календарь, как календарь по умолчанию.TrN   r   )r7   rk   z, c                 S   s   g | ]}d |j  d qS )r  )r   )r0   Zprojectr'   r'   r(   r2     s     z&CmfCalendar.delete.<locals>.<listcomp>uk   Невозможно удалить календарь, он используется в проектах: )r7   uY   Невозможно удалить календарь, он используется у u    пользователейzparent.namec                 S   s$   g | ]}d |j j d|j d qS )r  z: r!  )r0   Zslar'   r'   r(   r2     s     ue   Невозможно удалить календарь, он используется в целях: )r   r  rS   rV   Z
CmfProjectr~   joinZ	CmfPersoncountZCmfSDeskSlaGoalr  r  )r   r$  r%  ZprojectsZprojects_strZperson_countZ	sla_goalsZsla_goals_strr  r'   r(   r  y  s.    

zCmfCalendar.delete)NNN)NNr   )NNr   N)NNr   NNN)NNNNNN)NN)NN)NNT)N)NF)F)r   )>__name__
__module____qualname__r   r   Zui_meta_skipZapi_methodsstaticmethodDater   r)   r"   r9   r   rA   rE   rG   rI   Timer   r*  rT   rX   classmethodrd   r   r   boolr   r   r   r	   r  Zcmfrk   ZCmfTyper   r   Zon_server_eventr   r   r   r   r   r   r   r   r   r   r   r   Zcmf_deferred_jobr   r   r  r  r  r  r   r   r  __classcell__r'   r'   r  r(   r      s           !  
 *          3          /   
AM      
"

   .. F   4
(
9 3DGr   )r7   r4   r$   rR   	functoolsr   collectionsr   r   typingr   r   r   r   r	   r   ZpytzZcmf.includeZcommon.fieldsr   Zdateutil.relativedeltar   r   r  r4  r*   r3  r*  r   r'   r'   r'   r(   <module>   s6           