B
    f                 @   s   d dl Zd dlZd dlm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 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)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dg Zeeeee dddZ	edRee
e
e
eddd	ZedSeee
e
ee dddZedTeee
e
ee
 ee dddZedUeee
e
e
e
e
ee dddZedVeee
e
e
e
e
ee dddZedWeeeeef dddZedXe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dYd eeeed$d%d&Zeed'dZeed ej j!f eee"d)d*d+Z#ed[d eee
eef ed,d-d.Z$d\e
eed0d1d2Z%eee
d3d4d5Z&ed]d eee
d6d7d8Z'ed^d ee
ed9d:d;Z(ed<d= Z)ed>d? Z*ee+d#d#d@dAdBdC Z,dDdE Z-dFdG Z.dHdI Z/dJdK Z0 fdLdMZ1dNdO Z2 fdPdQZ3  Z4S )_CmfCalendarrecalc_calendarload_holidays)
date_startdate_endreturnc             c   s4   x.t t||  jd D ]}| t| V  qW 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>G   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%   )r(   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   sV   g }|dk	rd}| }x<|rP|dkr,|d8 }n
||kr6P | | |t|d7 }qW |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   )r7   r8   r9   r:   r!   current_dater%   r%   r&   	every_dayL   s    

zCmfCalendar.every_day)r7   r8   r9   r:   weekdaysr   c       	      C   s   g }|dk	rd}| t tdd }xj|rxR|D ]J}|t |d }|| krr|dkrZ|d8 }n||krhd}P || |s.P q.W |t |d7 }q$W |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]: список дат
        NTr0   )weekdayr   F)r5   )r   r   r;   )	r7   r8   r9   r:   r>   r!   r<   r?   r,   r%   r%   r&   
every_weekn   s$    


zCmfCalendar.every_week)r7   r8   r9   r:   r)   r*   r+   r   c             C   s   g }|dk	rd}| }x~|rt j||||d}	|	| krZ|dkrF|d8 }n
|	|krPP ||	 |jd | }
|j|
d  }|
d d }
|j||
d}qW |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   )r2   r-   )r   r6   r;   r-   r2   r3   )r7   r8   r9   r:   r)   r*   r+   r!   r<   r,   r-   r2   r%   r%   r&   every_month   s$    


zCmfCalendar.every_month)r7   r8   r9   r-   r)   r*   r+   r   c       
      C   s~   g }|dk	rd}| j |dd}xZ|rxtj||||d}	|	| krd|dkrP|d8 }n
|	|krZP ||	 |j |jd d}q W |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,   )r)   r*   r+   )r2   )r3   r   r6   r;   r2   )
r7   r8   r9   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      )hoursuo   Время начала должно быть раньше времени окончания интервалаT)abort)r"   r(   todaycombinetimer#   	cmf_alert)rC   rD   rH   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   rL   total_seconds)rC   rD   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]]: приоритет, список дат
        Nr=   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+   r@   r   c                s"   g | ]}t  d | dr|qS )r+   F)getattr)r.   r$   )rQ   r%   r&   r/   e  s    z2CmfCalendar.calc_excluded_days.<locals>.<listcomp>)r>   )period_start_datevalueperiod_end_dater:   Zrepeat_timesrepeat_typer=   rB   r-   r)   month_week_positionmonth_day_weekrA   r   r@   )	clsrQ   r!   priorityr7   r8   r:   r9   r>   r%   )rQ   r&   calc_excluded_days,  sH    






zCmfCalendar.calc_excluded_days)r4   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}x<|D ]4}| |\}	}
|	dkrnqR||
krR|	|krR|	}|}qRW |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}|rt|| djdkr|ddg |j}t|| dj}t|| dj}|dkr^| 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	   zintervals.*rV   z!=rS   z<=rU   z>=)parentfieldsfilterr   workc             s   s*   | ]"}|j jd |jjd gV  qdS )z%H:%MN)rC   rT   strftimerD   )r.   intervalr%   r%   r&   	<genexpr>  s   z*CmfCalendar.recalc_date.<locals>.<genexpr>_typedefaultzdefault_workweek.*zdefault_workweek.intervals.*_intervals_total_minutesc             3   s6   | ].}|j j kr|jjd |jjd gV  qdS )z%H:%MN)day_weekrT   rC   r`   rD   )r.   ra   )r?   r%   r&   rb     s   )r\   r(   )r?   rN   CmfCalendarExcludelistr[   exclude_typerT   intervals_total_minutesextend	intervalsCmfCalendarWorkWeekgetrR   load_fieldsdefault_workweekCmfCalendarDayrf   r2   r-   r,   day_typeinterval_total_minutesinterval_jsonsave)rY   r4   r(   r,   rl   day_num
exclusionsZexclusion_priorityrQ   rZ   Zexcluded_daysrr   rs   calendar_dayr%   )r?   r&   recalc_daten  s`    



zCmfCalendar.recalc_dateT)r4   r   r   back_recalcr   c             C   sl   |dkr$t j   }|jddd}|dkrF|tdd t jdd }x | ||D ]}| || qTW dS )u$  
        Пересчет дней календаря
        Если интервал не задан, делает пересчет от начала текущего года +2 года, итого 3 года

        Args:
            calendar (CmfCalendar): объект календаря
            date_start (Date, optional): начальная дата
            date_end (Date, optional): конечная дата
            back_recalc (bool, optional): пересчет назад только текущего года
        Nr   )r-   r,   r   )years)r!   )r"   rH   r(   r3   r   r#   r'   ry   )rY   r4   r   r   rz   rH   r(   r%   r%   r&   recalc_calendar_day  s    zCmfCalendar.recalc_calendar_day   F)r4   r(   detailr   c             C   sx   t |d}tjjdd|gdd|ggdddgd}|sdt j|d	gd
}td|dd|j ddd |j|j	|j
i dS )u<  
        Получает день календаря

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

        Returns:
            day (dict): информация о дне календаря
        r   Z	parent_id=r(   rr   rs   rt   )r^   r]   dirty)r]   u	   День z%d.%m.%Yu*    не найден в календаре "uO   ". Попробуйте запустить пересчет календаря.T)rG   )typerl   rj   r~   )ZcmfutilZget_obj_id_by_anyrN   rq   sgetZget_obj_by_idrK   namerr   rt   rs   )rY   r4   r(   r~   Zcalendar_idr,   r%   r%   r&   get_day  s    )zCmfCalendar.get_day)r4   r2   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 }|}x>||kr| |j|j|j	ddg d||< |t j
dd7 }qdW |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}x~| D ]r\}	}
|	 d }t|| dj}t|| dj}||
d< ||
d< |dkrfdd|jD |
d< qW x|D ]}|jj}x||jkrZd}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 }||}|rF||d< ||d< ||d< |t j
dd7 }qW qW t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 }xt|D ]v\}}}d}g }|jj}|dkr|jj}d d |jD }x6|D ].}	||	}|r||d< ||d< ||d< qW qW tjj||d
gd!}xl|D ]d}||jjd}
|
rj|
dg   x"|
 D ]\}}t||| qW |jrj|j d"d# qjW xP| D ]D\}	}
tj||	d$}x"|
 D ]\}}t||| qW |   qW dS )%uQ  
        Расчитывает все дни года с учетом рабочих недель и исключений и сохраняет их в БД

        Args:
            calendar (CmfCalendar): объект календаря
            year (Union[str, int, Date, Datetime], optional): год. Defaults to None.
        Nr   r      r   )rf   r2   r-   r,   rr   rs   rt   )r!   zdefault_workweek.*zdefault_workweek.intervals.*r	   zintervals.*ORrS   z>=z<=rU   )r\   r]   r^   r,   rc   re   rr   rs   r_   c                s4   g | ],}|j j kr|jjd |jjd gqS )z%H:%M)rf   rT   rC   r`   rD   )r.   ra   )r?   r%   r&   r/   J  s   z-CmfCalendar.calc_one_year.<locals>.<listcomp>rt   rd   c                s4   g | ],}|j j kr|jjd |jjd gqS )z%H:%M)rf   rT   rC   r`   rD   )r.   ra   )r?   r%   r&   r/   `  s   rV   z!=c                s"   g | ]}|j r ||f qS r%   )rV   r[   )r.   rQ   )rY   r%   r&   r/   |  s   c             S   s(   g | ] }|j jd |jjd gqS )z%H:%M)rC   rT   r`   rD   )r.   ra   r%   r%   r&   r/     s   )r\   r2   r]   T)	only_data)r\   r(   )!r"   nowr2   
isinstancer(   r    dictr?   r-   r,   r#   ro   rp   rN   rm   rh   itemsrR   rT   rl   rS   rU   rn   rg   sortedri   rj   rq   popsortsetattr
is_changedru   )rY   r4   r2   Zstart_date_yearZend_date_yearZdays_of_yearr<   rp   	workweeksr(   Zday_datarv   rr   rs   workweekZcurrent_period_daterl   Zday_of_yearrw   Zexcluded_dates_datesrQ   Zcalendar_daysrx   Z
field_namerT   r%   )rY   r?   r&   calc_one_year  s    

















zCmfCalendar.calc_one_yearr   )
back_yearsforcer   c             C   s   |dk s|dkrt ddd | ddg | js8|s8dS tj j}|rP|| n|}|t| j }x4t||D ]&}t	j
| | tt|d	d
| _qnW t	j
j  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   r   u@   Параметр back_years должен быть от 0 до 10T)rG   r   calc_num_yearsNr   r   F)r   )rK   ro   r   r"   r   r2   r    r   r   rN   r   r   r(   
calc_untilr   cache_clearru   )selfr   r   current_yearZ	from_yearZto_yearr2   r%   r%   r&   r     s    
zCmfCalendar.recalc_calendar)from_dtto_dtr   c             C   s`  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}x|D ]}|j|kr|j|kr||jd 7 }qlx|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 rHt||  nd}||7 }qW qlW |S )u  
        Получает дельту рабочего времени в секундах между начальной и конечной датой и временем

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

        Returns:
            work_timedelta (int): дельта рабочего времени в секундах
        r   )secondsr\   r   rr   r_   r(   z>=z<=rs   rt   )r^   r]   rM   z%H:%Mr   )tzinfo)r!   )r"   timezoner#   r    r(   rN   rq   Zslistrs   rt   strptimerJ   rI   maxminrO   )r   r   r   Zwork_timedeltatzr7   r8   	work_dayswork_dayra   rC   rD   interval_startinterval_endstartendZtime_periodr%   r%   r&   get_work_timedelta  s2    


zCmfCalendar.get_work_timedelta)r4   r   r   r   c             C   s  dd }d}t t jt|jpdd}| }| }	x||	kr| ||}
|ol|| kpl|| k}|
d dkr|s|t jdd7 }q<|r|}|
}xV|d dkr|| kr|t jdd7 }n|| kr|t jdd8 }| ||}qW |}
|| kr2|| kr2||
d	 7 }|t jdd7 }q<x|
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 }q<W |t jdd7 }q<W |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 )NrM   r   )r   r   r    rO   )r   r   r   r   r   r   r%   r%   r&   	intersect  s
    

z3CmfCalendar.get_duration_minutes.<locals>.intersectr   )r   r   r_   r   )r!   rj   rl   z%H:%M)r   )	r"   r   r#   r    r(   r   r   rJ   rI   )rY   r4   r   r   force_include_endsr   Zminutes_sumr   r<   r8   r,   Zforce_includeZcur_dateZcur_dayra   rC   rD   r   r   Zinterr%   r%   r&   get_duration_minutes  sH    	z CmfCalendar.get_duration_minutes)r4   r   durationr   c             C   s  |s|S t t jt|jpdd}d}|dk r<d}t|}| }x*|dkrp| ||}|sj|d ntt|d }x|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rP qW |t j|sddndd	7 }qHW |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   FTrl   z%H:%Mr   )r   )r!   rM   r0   )Zminutes)r"   r   r#   r    absr(   r   rh   reversedr   rJ   rI   r   r   rO   )rY   r4   r   r   r   reverser<   r,   rl   ra   rC   rD   r   r   r   r   r   r%   r%   r&   get_date_by_durationA  s8    
&z CmfCalendar.get_date_by_durationc             C   sT  t jj| | d}ttjt| jp&dd}|d }t|d }x|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rdS |

 t
ddkrtjj|
tjj
 |d}
||	kr||
k rdS ||	kr,d	S ||
krD|dkrDd	S |d8 }qLW dS )
u=   
        Вычисляет флаг calendar_paused
        )r4   r(   r   )r   rl   r   z%H:%M)r   FT)rN   r   r   r(   r"   r   r#   r    lenr   rJ   rI   r   )r4   
check_dater,   r   rl   r$   ra   rC   rD   r   r   r%   r%   r&   get_sla_cycle_calendar_pausedv  s,    (


z)CmfCalendar.get_sla_cycle_calendar_pausedc             C   s  t jj| | d}ttjt| jp&dd}|d }d}t|d }x0|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|tjdd	7 }|jdddd
}|}P |
 t
ddkr,tjj|tjj
 |d}||
krH||k rH|}P n(||
krZ|
}P n||krp|dkrpP |d8 }qPW tjj|tjj
 |d}|tjdd7 }|stj| |d}|S )uY    
        Вычисляет next_time_update - сл дата пересчета
        )r4   r(   r   )r   rl   Nr   z%H:%M)r   )rF   )ZminutesecondZmicrosecond)r!   )r4   r   )rN   r   r   r(   r"   r   r#   r    r   r   rJ   rI   r3   r   r   get_sla_cycle_next_time_update)r4   r   r,   r   rl   resultr$   ra   rC   rD   r   r   r%   r%   r&   r     s@    (


z*CmfCalendar.get_sla_cycle_next_time_updatez	@minutely)Z	only_onceZ
system_jobZschedulec           	   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\}}x|tjj||||| gd}|sfP ||7 }xN|D ]F}|j|_tj|j	j
|jjd|_tj|j	j
|jjd|j_|  qtW qFW d S )Nnext_time_updatez<=Z	stop_timer   ZNULLZ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   )r^   r]   slice)r4   r   )r"   r   rN   ZCmfSDeskSlaCyclerh   r   r   r   r   r   r4   rT   r   r   ru   )r   r^   r]   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}xH|D ]>}|tjdd	t	|
d
}| slqBt|ddd}t| }	W dQ R X |	d}
||
krtd|
 d| d|  qBg }dd |	dD }|	d}|	d}xD|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%}|rq|d&}|d'}|r||}n||}d}tt fd(d)|d}tt fd*d)|d}|r|d+ }n|r|d, }|rNt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;}x"| D ]\}}t ||| qW |j!rd$|_!|j"r|j#d4d< |dkr|$d=g | $d>g | j%}|rJ| }n| }|j&} tj'j(||d9gd?}!|| |!}"x|"D ]\\}#}$|$r|#stj)|d;}#|$j*|#_*|$j+|#_+|$j,|#_,n|#rd4|#_!|#j"r||#j#d4d< q|W n0|$d@g x"|j&D ]}#d4|#_!|#j#d4d< qW |-  |.  |j#d4d< |/| qW tjj(d6dA|gd6d7d.| dBgdCdd4ggdD}%x|%D ]}|j0d4dE qlW qBW | $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contribr4   z.jsonrzutf-8)encodingNr2   u   Год (uL   ) указанный в файле не соответствует году u    в имени файла c             S   s   i | ]}|d  |d qS )r   idr%   )r.   holidayr%   r%   r&   
<dictcomp>  s    z-CmfCalendar.load_holidays.<locals>.<dictcomp>holidays	transfersr   r(   .z%Y.%m.%d r\   r   rV   r=   r:   r   rS   rU   F)r^   is_autor   r   c                s   |  d kS )Nfrom)rn   )item)date_r%   r&   <lambda>>      z+CmfCalendar.load_holidays.<locals>.<lambda>c                s   |  d kS )Nto)rn   )r   )r   r%   r&   r   ?  r   r   r   u    за Zexclude_z%Y_%m_%d:z ()r   T)
rV   r   r   rS   rU   r-   r)   rX   rW   r   r   ZLIKE%r	   )r^   r]   Zinclude_deleted)r\   )r   zintervals.*rp   )r\   rf   r]   rl   zNOT INz_%r   )r^   )r   r   )2	itertoolsr   Zpathlibr   r"   r   r2   ZconfigZ
CMF_FOLDERstrZwith_suffixexistsopenZjsonloadsreadrn   ZloggingZwarningr   r(   r,   MONTHS_SHORTr-   rN   rg   r   nextr^   r   r?   r   r   Zcmf_deletedr   ru   ro   rp   rl   ZCmfCalendarWorkWeekIntervalrh   ZCmfCalendarExcludeIntervalrC   rD   Zinterval_minutesZ_calc_intervals_total_minutesZ_calc_exclude_typer;   delete_set_as_dirty)&r   r   r   Z	day_typesr   r{   r2   Z	file_pathfZcalendar_dataZcalendar_yearZexclude_codesr   r   r   Z	date_datar(   Zdate_strZexcluderr   r   r   ZtransferZtransfer_fromZtransfer_toZtransfer_dater   Zexclude_dataZfieldrT   rp   r?   Zexclude_intervalsZworkweek_intervalsrl   Zexclude_intervalZworkweek_intervalZexcludesr%   )r   r&   r     s    







$





zCmfCalendar.load_holidaysc             C   s   d| _ tjj  dS )u   
        Помечает, что календарь необходимо пересчитать и сбрасывает кэш дней
        TN)r   rN   r   r   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}|rtd|_ |jdd d|j d| }t| dS )uX   
        Изменяет флаг "Календарь по умолчанию"
        Nu   Для сброса флага "Календарь по умолчанию" вы должны установить его у любого другого календаря.T)rG   uf   Во всех новых проектах будет использоваться календарь ""r   z!=
is_defaultr   )r^   F)r   u^   Сбросили флаг "Календарь по умолчанию" у календаря "z". )	r   r   rK   r   rN   r   rn   r   ru   )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| _dS )u}   
        Исправляет нулевые значения, если приходит null выставляет 0
        r   N)r   Zis_nullr   )r   r%   r%   r&   fix_null_value  s    zCmfCalendar.fix_null_valuec                s  | j rt| dd rtjj| jdddgd}|j| _|j| _xv|jD ]l}|	 }| |_
d|_|jdd |d	g x(|jD ]}|	 }||_
|jdd q~W |jsF|jsF|| _qFW x|jD ]T}|	 }	| |	_
|	jdd |d	g x(|jD ]}|	 }|	|_
|jdd qW qW nrtj  j}
ttj|
  | _tj| d
d}x&tdD ]}t|d| dd qZW |jdd || _|   |   t| jdkrtddd | jj r| !  t" j||S )Ntemplater	   zworkweeks.*zexclusions.*)r   r]   FT)r   zintervals.*u   По умолчанию)r\   r   r   r,   rc   Zweekendr   u\   Количество лет расчета не должно быть больше 10 лет)rG   )#Zis_newrR   rN   r   rn   r   r   r   r   Zcloner\   systemru   ro   rl   rS   rU   rp   rw   r"   r   Z
astimezoner   r    Z	utcoffsetrO   rm   r   r   r   r   rK   r   r   super)r   argskwargsZtemplate_calendarZtemplate_workweekZnew_workweekZtemplate_intervalZnew_intervalZtemplate_exclusionZnew_exclusionr   r   r$   )	__class__r%   r&   ru     sT    

zCmfCalendar.savec          	   c   s  dd }d}|}t g }d }d}	d}
xn|sddd|gdd	d
ggg}|rt|	s\|dd	|g |rt|
st|dd	|g |tjjdd	| g|gdddddgdgddgd |std| j d| j	  |
 }|j|kr|jd
kr||}|j|_|j|_d}	|j|krN|jd
krJ|r2|j|_|j|_n||}|j|_|j|_d}
|jd
kr^|}|V  t|jjtj tjdd  }|d7 }q(W d S )Nc             S   s0   x*t t| D ]}| | jdkr| | S qW d S )Nr_   )r   r   rr   )bufferr$   r%   r%   r&   _get_next_work_day  s    z1CmfCalendar.work_days.<locals>._get_next_work_dayr   Fr   r(   z>=rr   z==r_   r\   rt   rs   zparent.timezoner   )r^   r]   Zorder_byr   u   Календарь "u   " рассчитан до Tr   )r!   )r   r;   rk   rN   rq   rh   StopIterationr   r   loadpopleftr(   rr   rt   rs   DatetimerI   rT   r   rJ   r"   r#   )r   r7   r   Zfinish_dater   nZ	next_dater   Zprev_work_dayZstart_date_givenZfinish_date_givenZdate_filterr   Znext_work_dayr%   r%   r&   r     sX    

&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
||S )Nr   u   Нельзя удалить календарь по умолчанию. Сначала назначьте любой другой календарь, как календарь по умолчанию.T)rG   r   )r4   r]   z, c             S   s   g | ]}d |j  d qS )r   )r   )r.   Zprojectr%   r%   r&   r/   Z  s    z&CmfCalendar.delete.<locals>.<listcomp>uk   Невозможно удалить календарь, он используется в проектах: )r4   uY   Невозможно удалить календарь, он используется у u    пользователей)ro   r   rK   rN   Z
CmfProjectrh   joinZ	CmfPersoncountr   r   )r   r   r   ZprojectsZprojects_strZperson_count)r   r%   r&   r   Q  s    
zCmfCalendar.delete)NNN)NNr   )NNr   N)NNr   NNN)NNNNNN)NN)NN)NNT)F)N)r   F)F)r   )5__name__
__module____qualname__r
   r   Zapi_methodsstaticmethodDater   r'   r    r6   r   r=   r@   rA   rB   Timer   r   rL   rP   classmethodr[   r   ry   boolr|   r   r   r   Zcmfr]   ZCmfTyper   r   r   r   r   r   r   r   r   Zcmf_deferred_jobr   r   r   r   r   ru   r   r   __classcell__r%   r%   )r   r&   r      st       $)  $1  $- AM (" &.E3): 3
=Ar   )r4   r1   r"   	functoolsr   collectionsr   typingr   r   r   r   r   ZpytzZcmf.includeZcommon.fieldsr
   Zdateutil.relativedeltar   r   r   rJ   r   r(   r   r   r   r%   r%   r%   r&   <module>   s    