
    Y~hG                         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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                   j    e Zd Zej        j        ddgz   Zedededee         fd            Z	e	 	 d?ded	e
d
e
de
def
d            Ze	 	 d@dedede
de
dee         f
d            Ze	 	 dAdedede
de
dee
         dee         fd            Ze	 	 	 dBdedede
de
d	e
d
e
de
dee         fd            Ze	 	 	 dCdedede
de
d	e
d
e
de
dee         fd            Ze	 	 dDdededeeef         fd            ZedDdedede
fd            Zedddee
ee         f         fd            Zedd dedefd             Ze	 	 dEdd deded"edef
d#            Zee	 dFdeed ej         j!        f         ded%ede"fd&                        Z#e	 dGdd d'eee
eef         defd(            Z$dHd)e
d*edefd+Z%d,ed-ede
fd.Z&e	 dFdd d,ed-ede
fd/            Z'e	 dIdd d,ed1e
defd2            Z(ed3             Z)ed4             Z*e e+d!d!d56          d7                         Z,d8 Z-d9 Z.d: Z/d; Z0 fd<Z1d= Z2 fd>Z3 xZ4S )JCmfCalendarrecalc_calendarload_holidays
date_startdate_endreturnc              #      K   t          t          || z
  j                  dz             D ]}| t          j        |          z   V  dS )u   
        Генератор диапазона дат

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

        Yields:
            Iterator[Date]: дата
        r   N)rangeintdaysdatetime	timedelta)r   r   is      ./common/models/cmf_calendar.py_date_rangezCmfCalendar._date_range   s]       sHz17881<== 	5 	5Ax1!4444444	5 	5    Ndate	month_daypositionday_of_weekc                 F    |rDt          j         j         j                  d         }||k     r|}                     |          }nXt          j                    }|                     j         j                  } fd|D             }|dk     r||         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                 L    g | ] }|         j         j         k    |         !S  )month).0weekr+   r.   s     r(   
<listcomp>z=CmfCalendar._get_day_by_position_in_month.<locals>.<listcomp>G   s4    iii$4CTCZ^b^hChChD-ChChChr*   r   )cl
monthrangeyearr3   replaceCalendarmonthdatescalendar)	r+   r,   r-   r.   days_in_monthr0   calendarweeksday_of_weekss	   `  `     r(   _get_day_by_position_in_monthz)CmfCalendar._get_day_by_position_in_month-   s    $  		OM$)TZ@@CMy(()	,,9,--CC{}}H//	4:FFEiiiii%iiiL,4qLL,x((l2>NC
r*   r   
start_dateend_daterepeatsperiodc                     g }|d}| }|r9||dz  }n||k    rn*|                     |           |t          |          z  }|9|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   )rC   rD   rE   rF   r$   current_dates         r(   	every_dayzCmfCalendar.every_dayL   sx    " G! 	71((KK%%%Mv6666L  	7 r*   weekdaysc                    g }|d}| t          t          d                    z   }|r^|D ]F}|t          |          z   }|| k    r'||dz  }n
||k    rd} n|                    |           |s nG|t          |          z  }|^|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]: список дат
        NTr7   )weekdayr   F)r@   )r   r   rI   )	rC   rD   rE   rF   rL   r$   rJ   rN   r0   s	            r(   
every_weekzCmfCalendar.every_weekn   s    & G!M"R&&$A$A$AA 	8#  "]7%C%C%CC*$$'1x"'KK$$$ E M7777L  	8  r*   c                     g }|d}| }|rt                               ||||          }	|	| k    r$||dz  }n|	|k    rnP|                    |	           |j        dz
  |z   }
|j        |
dz  z   }|
dz  dz   }
|                    ||
          }||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:   r3   )r   rB   rI   r3   r:   r;   )rC   rD   rE   rF   r,   r-   r.   r$   rJ   r0   r3   r:   s               r(   every_monthzCmfCalendar.every_month   s    4 G! 	H;;LT]EM[f < h hC j  #qLGG8^^C    &*V3E$u{2DBJNE'//T/GGL!  	H$ r*   r3   c                    g }|d}|                      |d          }|rht                              ||||          }	|	| k    r$||dz  }n|	|k    rn5|                    |	           |                     |j        dz             }|h|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   r3   r0   rQ   )r:   )r;   r   rB   rI   r:   )
rC   rD   rE   r3   r,   r-   r.   r$   rJ   r0   s
             r(   
every_yearzCmfCalendar.every_year   s    4 G!))1)== 	L;;LT]EM[f < h hC j  #qLGG8^^C   '//\5F5J/KKL  	L r*   	from_timeto_timec                 z   t           j                                        }t           j                             ||           } t           j                             ||          }t          j        dd          |                                k    r|t          j        d          z  }| |k    rt          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)rV   rW   r^   s      r(   normalize_time_intervalz#CmfCalendar.normalize_time_interval   s      ##%%%--eY??	#++E7;;=A',,..00x)3333G  H  PT  U  U  U  U'!!r*   c                     | r|sdS t           j                            | |          \  } }|| z
                                  dz  S )u?  
        Вычисляет в минутах интервал времени

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

        Returns:
            int: минуты
        r   <   )modelsr   rb   total_seconds)rV   rW   s     r(   get_interval_minutesz CmfCalendar.get_interval_minutes  sR      	 	1#/GG	SZ[[	7)#2244r99r*   	exclusionzmodels.CmfCalendarExcludec                 >   g }d}j         j        }j        j        }j        }j        }j        dk    r&j        dk    rd}|                     |||          }n>j        dk    r(d}|                     |||j        j	                  }nj        dk    r-d	}|                     |||j        j
        j        
          }nӉj        dk    r"d}|                     ||||j	                  }nj        dk    r(d}|                     ||||j
        j                  }nsj        dk    r8d}fdt          d          D             }|                     |||||          }n0j        dk    r%j        dk    rd}|                     ||||          }||fS )uw  
        Расчитывает и возвращает исключительные дни и приоритет исключения
        Приоритеты исключений (7-самый высокий, 1-самый низкий):
            7 - Ежедневно (каждый день)
            6 - Ежегодно (в указанный день)
            5 - Ежегодно (по позиции дня недели месяца)
            4 - Ежемесячно (в указанный день)
            3 - Ежемесячно (по позиции дня недели)
            2 - Еженедельно
            1 - Ежедневно (каждый N день)

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

        Returns:
            Tuple[int, List[Date]]: приоритет, список дат
        NrK   r   r   every_year_dayr   )r3   r,   every_year_week_dayr   )r3   r-   r.   every_month_dayr   )r,   every_month_week_dayr   )r-   r.   rO   r   c                 <    g | ]}t          d | d          |S )r.   F)getattr)r4   r'   rh   s     r(   r6   z2CmfCalendar.calc_excluded_days.<locals>.<listcomp>e  s5    \\\awyBSPQBSBSUZ/[/[\\\\r*   )rL   )period_start_datevalueperiod_end_daterF   repeat_timesrepeat_typerK   rU   r3   r,   month_week_positionmonth_day_weekrR   r"   rO   )	clsrh   r$   priorityrC   rD   rF   rE   rL   s	    `       r(   calc_excluded_dayszCmfCalendar.calc_excluded_days,  s
   & 06
,2!( K//I4D4I4IH==Xw??DD"&666H>>*h(19CV " X XDD"&;;;H>>*h(1+4+H.7.F " H HDD "&777H??:x#)Y5H # J JDD"&<<<H??:x&,5,I/8/G # I IDD "l22H\\\\588\\\H>>*hRZ>[[DD"k11i6F!6K6KH==XwGGD~r*   r?   c           	      8   d}g }|                                 d }t          j                            |ddgg ddd|gdd	|gg
          }d}|D ]+}|                     |          \  }	}
|	||
v r
|	|k    r|	}|},|rC|j        j        }|j        j        }|dk    r$|                    d |j	        D                        nt          j
                            |ddgdd|gdd	|gg
          }|rt          || d          j        dk    r|                    ddg           |j        }t          || d          j        }t          || d          j        }|dk    r&|                    fd|j	        D                        t          j                            ||          }|sGt                              ||          }|_        |j        |_        |j        |_        |j        |_        ||_        ||_        ||_        |                                 |S )u   
        Пересчет указанного дня календаря

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

        Returns:
            NoReturn
        Nr0   r
   intervals.*rt   !=Nrp   <=rr   >=parentfieldsfilterr   workc              3      K   | ]B}|j         j                            d           |j        j                            d           gV  CdS %H:%MNrV   rq   strftimerW   r4   intervals     r(   	<genexpr>z*CmfCalendar.recalc_date.<locals>.<genexpr>  sl       !@ !@%- #+"4":"C"CG"L"LhN^NdNmNmnuNvNv!w !@ !@ !@ !@ !@ !@r*   _typedefaultdefault_workweek.*default_workweek.intervals.*_intervals_total_minutesc              3      K   | ]R}|j         j        k    |j        j                            d           |j        j                            d           gV  SdS r   day_weekrq   rV   r   rW   r4   r   rN   s     r(   r   z*CmfCalendar.recalc_date.<locals>.<genexpr>  sr       !f !f%-(BSBY]dBdBd #+"4":"C"CG"L"LhN^NdNmNmnuNvNv!wBdBdBdBd!f !fr*   r   r+   )rN   re   CmfCalendarExcludelistry   exclude_typerq   intervals_total_minutesextend	intervalsCmfCalendarWorkWeekgetro   load_fieldsdefault_workweekCmfCalendarDayr   r:   r3   r0   day_typeinterval_total_minutesinterval_jsonsave)rw   r?   r+   r0   r   day_num
exclusionsexclusion_priorityrh   rx   excluded_daysr   r   calendar_dayrN   s                 @r(   recalc_datezCmfCalendar.recalc_daten  s    	,,..!// .33'+++$dD1"D$/ 4 
 

 # 	  	 I&)&<&<Y&G&G#Hm}$$4F)F)F%-" 	f'-H%(%@%F"6!!   !@ !@14!@ !@ !@ @ @ @ ,00]+,dD9<MtUY;Z[ 1  C  0'#''8'8'899?9LL$$&:<Z%[\\\/sw$5$5$566<H%,SW2V2V2V%W%W%]"6!!   !f !f !f !f14!f !f !f f f f ,00t0LL 	(!00t0LLL$+L! $	L!%L#xL (.D+%."r*   Tback_recalcc                 L   |Gt           j                                                                         }|                    dd          }|)|t	          d          z   t          j        d          z
  }|                     ||          D ]}|                     ||           dS )u$  
        Пересчет дней календаря
        Если интервал не задан, делает пересчет от начала текущего года +2 года, итого 3 года

        Args:
            calendar (CmfCalendar): объект календаря
            date_start (Date, optional): начальная дата
            date_end (Date, optional): конечная дата
            back_recalc (bool, optional): пересчет назад только текущего года
        Nr   rT   r   )yearsrH   )r%   r^   r+   r;   r   r&   r)   r   )rw   r?   r   r   r   r^   r+   s          r(   recalc_calendar_dayzCmfCalendar.recalc_calendar_day  s     %++--2244EQA66J!M$:$:$::X=OUV=W=W=WWHOOJ99 	, 	,DOOHd++++	, 	,r*   Fdetailc                 4   t                               |d          }t          j                            dd|gdd|ggg d          }|s;t                               |dg          }t          d	|d
d|j         dd           |j        |j	        |j
        i dS )u<  
        Получает день календаря

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

        Returns:
            day (dict): информация о дне календаря
        r   	parent_id=r+   )r   r   r   r   r   dirty)r   u	   День z%d.%m.%Yu*    не найден в календаре "uO   ". Попробуйте запустить пересчет календаря.Tr\   )typer   r   r   )cmfutilget_obj_id_by_anyre   r   sgetget_obj_by_idra   namer   r   r   )rw   r?   r+   r   calendar_idr0   s         r(   get_dayzCmfCalendar.get_day  s     //-HH#(( #{3fc45HIJJJ ) 
 

  	t,,[',KKH g$l g g[c[h g g gnrt t t t L*'*'A	
 
 	
r*   r:   c                 	    |$t           j                                         j        }n;t          |t           j                  st          |t           j                   r|j        }t          j        t          |          dd          }t          j        t          |          dd          }t                      }|}||k    rL|                                |j        |j        |j	        ddg d||<   |t          j
        d          z  }||k    L|                    dd	g           |j        }t          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 ]t\  }	}
|	                                d }t#          || d          j        }t#          || d          j        }||
d<   ||
d<   |dk    rfd|j        D             |
d<   u|D ]}|j        j        }||j        k    rd}g }|                                d }t#          || d          j        }|dk    r|t          j
        d          z  }_|dk    r+t#          || d          j        }fd|j        D             }|                    |          }|r||d<   ||d<   ||d<   |t          j
        d          z  }||j        k    Όt          j                            |d
dgg dddd|gdd|ggdd|gdd|ggdd|gdd|gggg          } fd|D             }t1          |          D ]d\  }}}d}g }|j        j        }|dk    r|j        j        }d |j        D             }|D ](}	|                    |	          }|r||d<   ||d<   ||d<   )et          j                            ||d
g          }|D ]}|                    |j        j        d          }
|
rp|
                    dg                                            |
                                D ]\  }}t=          |||           |j        r|                     d !           |                                D ]`\  }	}
t                              ||	"          }|
                                D ]\  }}t=          |||           |                                  adS )#uQ  
        Расчитывает все дни года с учетом рабочих недель и исключений и сохраняет их в БД

        Args:
            calendar (CmfCalendar): объект календаря
            year (Union[str, int, Date, Datetime], optional): год. Defaults to None.
        Nr   r      r   )r   r:   r3   r0   r   r   r   rH   r   r   r
   r{   ORrp   r   r~   rr   r   r0   r   r   r   r   r   c                     g | ]P}|j         j        k    |j        j                            d           |j        j                            d           gQS r   r   r   s     r(   r6   z-CmfCalendar.calc_one_year.<locals>.<listcomp>/  sb     - - - (BSBY]dBdBd '-66w??AQAWA`A`ahAiAijBdBdBdr*   r   r   c                     g | ]P}|j         j        k    |j        j                            d           |j        j                            d           gQS r   r   r   s     r(   r6   z-CmfCalendar.calc_one_year.<locals>.<listcomp>E  sb     ! ! !$h>O>UY`>`>` "+1::7CCXEUE[EdEdelEmEmn>`>`>`r*   r|   c                 P    g | ]"}|j         	                    |          |fz   #S r2   )rt   ry   )r4   rh   rw   s     r(   r6   z-CmfCalendar.calc_one_year.<locals>.<listcomp>b  sF     4 4 4'&24#00;;ylJ 4 4 4r*   c                     g | ]@}|j         j                            d           |j        j                            d           gAS r   r   r   s     r(   r6   z-CmfCalendar.calc_one_year.<locals>.<listcomp>k  sV         '-66w??AQAWA`A`ahAiAij  r*   )r   r:   r   T	only_datar   )!r%   nowr:   
isinstancer+   r#   dictrN   r3   r0   r&   r   r   re   r   r   itemsro   rq   r   rp   rr   r   r   sortedr   r   r   popsortsetattr
is_changedr   )rw   r?   r:   start_date_yearend_date_yeardays_of_yearrJ   r   	workweeksr+   day_datar   r   r   workweekcurrent_period_dater   day_of_yearr   excluded_dates_datesrh   calendar_daysr   
field_namerq   rN   s   `                          @r(   calc_one_yearzCmfCalendar.calc_one_year  s{    <$((**/DDhm,, 	
4AR0S0S 	9D"-D		1a88 c$iiR88 vv&m++(0022$)%+#' *+!#* *L& H.A6666L m++ 	24RSTTT#4.33'%t_=@SUY[h?ij#T?;>OQUWd=ef%t_=@QSWYf?gh	 4 	
 	
	 +0022 	 	ND(llnnG%GooG/G1B1B1BCCIH%,-='?c?c?c%d%d%j"#+HZ 1GH-.6!!- - - -$4$>- - -) " 	B 	BH"*"<"B%)AAA)*&	-5577)//"8->->->??Ey(('8+=1+E+E+EE'v%%-4X'?c?c?c-d-d-j*! ! ! !(0(:! ! !I
 +../BCC =.6K
+<RK 893<K0#x'9q'A'A'AA#1 &)AAA6 .33'+++)4ADWY]_lCmn'?BSUY[hAij)4ADUW[]jCkl	 4 
 

4 4 4 4+54 4 4 $*.#9#9 	= 	=Aui%&"I -3H6!!)2)J)P& $-$7  	  = =*..t44 =.6K
+<RK 893<K0= -22(WZV[2\\) 	6 	6L#''(9(?FFH 6_b1166888)1)9)9 = =%JL*e<<<<* 6 %%%555*0022 	  	 ND(!00t0LLL%-^^%5%5 9 9!
Ej%8888	  	 r*   
back_yearsforcec                    |                      g d           |t          | j                  }|dk     s|dk    rt          dd           | j        s|sdS t
          j                                        j        }|r||z
  n|}|t          | j                  z   }d}t          ||          D ]o}|s#t          j
        t          |          dd          }t          j                            | |           t          j
        t          |          d	d
          | _        p|| _        t          j        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 до 10Tr\   r   r   r   Fr   )r   r#   r   ra   r   r%   r   r:   r   r"   r+   re   r   r   
calc_until	calc_fromr   cache_clearr   )selfr   r   current_year	from_yearto_yearr   r:   s           r(   r   zCmfCalendar.recalc_calendar  sh     	KKKLLLT566J>>Z"__X`deeee
 	e 	F(,,..31;ML:--	T%8!9!99	)W-- 	? 	?D ;$M#d))Q::	,,T4888&mCIIr2>>DOO" 	"..000
		D	!!!!!r*   from_dtto_dtc                 F   d}t          j        t          j        t          | j        pd                              }|                                }|                                }t
          j                            dd| gg ddd|gdd|ggg d	
          }|D ]}|j        |k    r|j        |k    r||j        dz  z  }'|j	        D ]U}	t           j         
                    |	d         d                                          }
t           j         
                    |	d         d                                          }t           j                             |j        |
|          }t           j                             |j        ||          }|                                t          j        dd          k    r|t          j        d          z  }t          ||          }t          ||          }||k     r$t          ||z
                                            nd}||z  }W|S )u  
        Получает дельту рабочего времени в секундах между начальной и конечной датой и временем

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

        Returns:
            work_timedelta (int): дельта рабочего времени в секундах
        r   secondsr   r   )r   r   r   r+   r   r~   )r+   r   r   r   rd   r   r   tzinforH   )r%   timezoner&   r#   r+   re   r   slistr   r   strptimer`   r_   maxminrf   )r   r   r   work_timedeltatzrC   rD   	work_dayswork_dayr   rV   rW   interval_startinterval_endstartendtime_periods                    r(   get_work_timedeltazCmfCalendar.get_work_timedelta  s"    x1#dm>Pq:Q:QRRRSS\\^^
::<<)//3%)))z*VT8,D
 GFF 0 
 
	 " 	. 	.H}
**x}/H/H("AB"FF$2 
. 
.$-66x{GLLQQSS	"+44Xa['JJOOQQ!)!2!:!:8=)\^!:!_!_'088XZ8[[$$&&(-1*=*=== H$6A$>$>$>>LG^44%..DICKKc3;"="="?"?@@@UV+-
. r*   c                    d }d}t          j        t          j        t          |j        pd                              }|                                }|                                }	||	k    r|                     ||          }
|o/||                                k    p||                                k    }|
d         dk    r|s|t          j        d          z  }v|r|}|
}|d         dk    r||                                k    r|t          j        d          z  }n0||                                k    r|t          j        d          z  }|                     ||          }|d         dk    |}
||                                k    r=||                                k    r%||
d         z  }|t          j        d          z  }b|
d	         D ]}t           j                             |d         d
                                          }t           j                             |d         d
                                          }t           j                             |||          }t           j                             |||          }|                                t          j        dd          k    r|t          j        d          z  } |||||          }||z  }|t          j        d          z  }||	k    |S )u  
        Получает продолжительность рабочего времени в минутах из указанного диапазона дат

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

        Returns:
            minutes_sum (int): сумма минут
        c                     t          | |          }t          ||          }||k     r't          ||z
                                  dz            S dS )Nrd   r   )r   r   r#   rf   )r   r   r   r   r   r   s         r(   	intersectz3CmfCalendar.get_duration_minutes.<locals>.intersect  sQ    00Ee\**Cs{{C%K66882=>>>qr*   r   r   r   r   r   rH   r   r   r   r   )	r%   r   r&   r#   r+   r   r   r`   r_   )rw   r?   r   r   force_include_endsr  minutes_sumr   rJ   rD   r0   force_includecur_datecur_dayr   rV   rW   r   r   inters                       r(   get_duration_minutesz CmfCalendar.get_duration_minutes  s   	 	 	 x1#h>O>TST:U:UVVVWW||~~::<<h&&++h55C.sLGLLNN4R4rVbfkfpfpfrfrVrM6{f$$]$ 2 : : ::  (fo//#w||~~55 H$6A$>$>$>>%55 H$6A$>$>$>>!kk(H==G fo// w||~~--,%**,,2N2Ns#<== 2 : : ::, % %$-66x{GLLQQSS	"+44Xa['JJOOQQ!)!2!:!:<[]!:!^!^'088wWY8ZZ$$&&(-1*=*=== H$6A$>$>$>>L!	'5.,OOu$H.A6666LK h&&N r*   r   durationc                    |s|S t          j        t          j        t          |j        pd                              }d}|dk     rd}t	          |          }|                                }|dk    r|                     ||          }|s|d         n!t          t          |d                             }|D ]\}	t           j         	                    |	d         d          
                                }
t           j         	                    |	d         d          
                                }t           j                             ||
|          }t           j                             |||          }|
                                t          j
        dd          k    r|t          j        d	          z  }|st          ||          n|}|rt          ||          n|}|||k     r't          ||z
                                  d
z            ndz  }|dk    r n^|t          j        |sdnd	          z  }|dk    |r&|t          j        t	          |                    z   }n%|t          j        t	          |                    z
  }|S )u$  
        Рассчитывает дату и время на основе длительности от указанной даты и времени

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

        Returns:
            to_dt (Datetime): рассчитанная дата и время
        r   r   FTr   r   r   r   rH   rd   r7   )minutes)r%   r   r&   r#   absr+   r   r   reversedr   r`   r_   r   r   rf   )rw   r?   r   r  r   reverserJ   r0   r   r   rV   rW   r   r   r   r   r   s                    r(   get_date_by_durationz CmfCalendar.get_date_by_duration/  s|     	Nx1#h>O>TST:U:UVVVWWa<<G8}}H||~~ll++h55C07]K((T(3{K[B\B\=]=]I%  $-66x{GLLQQSS	"+44Xa['JJOOQQ!)!2!:!:<[]!:!^!^'088wWY8ZZ$$&&(-1*=*=== H$6A$>$>$>>L<CWG^4444;Mc'<000us{{Cu ; ; = = BCCCXYYq==E ! H.W4LAA"MMMML' ll*  	M"X%7H%N%N%NNEE 8#5c(mm#L#L#LLEr*   c                    t           j                            | |                                          }t	          j        t	          j        t          | j        pd                              }|d         }t          |          dz
  }|D ]}t          j        	                    |d         d          
                                }t          j        	                    |d         d          
                                }t          j                            |                                ||          }	t          j                            |                                ||          }
|	
                                t	          j
        dd          k    r.|

                                t	          j
        dd          k    r dS |

                                t	          j
        dd          k    rCt          j                            |
t          j        j        
                                |          }
||	k    r	||
k     r dS ||	k    r d	S ||
k    r	|dk    r d	S |dz  }dS )
u=   
        Вычисляет флаг calendar_paused
        r?   r+   r   r   r   r   r   r   FT)re   r   r   r+   r%   r   r&   r#   lenr   r`   r_   r   )r?   
check_dater0   r   r   r'   r   rV   rW   r   r   s              r(   get_sla_cycle_calendar_pausedz)CmfCalendar.get_sla_cycle_calendar_pausedd  s     (((ARAR(SSx1#h>O>TST:U:UVVVWW$		NNQ! 	 	H )228A;HHMMOOI'00!gFFKKMMG%.66z7H7H)\^6__N#,44Z__5F5FXZ4[[L""$$a(;(;;;@Q@Q@S@SW_WdefhiWjWj@j@juu  ""hmAq&9&999'088xGXG\GaGaGcGcln8oo^++
\0I0I uu~--tt|++6644FAAur*   c                    t           j                            | |                                          }t	          j        t	          j        t          | j        pd                              }|d         }d}t          |          dz
  }|D ] }t          j        	                    |d         d          
                                }t          j        	                    |d         d          
                                }	t          j                            |                                ||          }
t          j                            |                                |	|          }|

                                t	          j
        dd          k    r_|
                                t	          j
        dd          k    r4|t	          j        d	          z  }|                    ddd
          }|} n|
                                t	          j
        dd          k    rCt          j                            |t          j        j        
                                |          }||
k    r
||k     r|} n||
k    r|
} n||k    r|dk    r n|dz  }t          j                            |t          j        j        
                                |          }|t	          j        d          z  }|st                              | |          }|S )uX   
        Вычисляет next_time_update - сл дата пересчета
        r  r   r   r   Nr   r   r   rZ   )minutesecondmicrosecondrH   r?   r  )re   r   r   r+   r%   r   r&   r#   r  r   r`   r_   r;   r   r   get_sla_cycle_next_time_update)r?   r  r0   r   r   resultr'   r   rV   rW   r   r   s               r(   r  z*CmfCalendar.get_sla_cycle_next_time_update  s     (((ARAR(SSx1#h>O>TST:U:UVVVWW$		NNQ! "	 "	H )228A;HHMMOOI'00!gFFKKMMG%.66z7H7H)\^6__N#,44Z__5F5FXZ4[[L ""$$a(;(;;;@Q@Q@S@SW_WdefhiWjWj@j@jh0q9999
'//qPQ/RR
#   ""hmAq&9&999'088xGXG\GaGaGcGcln8oo^++
\0I0I &~--'|++66 EFAA&..z8;L;P;U;U;W;W`b.cc
h(a0000
 	j ??^h?iiFr*   z	@minutely)	only_once
system_jobschedulec                     t           j                                         } dd| gg dg dg}g d}d\  }}	 t          j                            |||||z   g          }|sd S ||z  }|D ]}|j        |_        t                              |j	        j
        |j        j        	          |_        t                              |j	        j
        |j        j        	          |j        _        |                                 )
Nnext_time_updater~   )	stop_timer   NULL)currentr   T)	sla_goalzsla_goal.calendarzsla_goal.calendar.timezonecodepause_interval_stop_timepause_interval_start_timelast_time_updater"  calendar_paused)r   d   T)r   r   slicer  )r%   r   re   CmfSDeskSlaCycler   r"  r*  r   r  r&  r?   rq   r+  r  r   )r   r   r   r   limitcyclescycles          r(   celery_minutely_hookz CmfCalendar.celery_minutely_hook  sE    ##%%  s+&&&"""
: : : u	,11W\^cfk^kVl1mmF UNE  ).)?&(3(Q(Q[`[i[r  @E  @V  @\(Q  )]  )]%/:/Y/Ychcqcz  HM  H^  Hd/Y  0e  0e&,

	r*   c                 ~  & ddl m} ddlm} ddd}t          j                                        j        }||dz   f}|D ]} |t          j        dd	t          |                    
                    d
          }|                                sPt          |dd          5 }t                              |                                          }	ddd           n# 1 swxY w Y   |	                    d          }
||
k    r$t"                              d|
 d| d|            g }d |	                    d          D             }|	                    d          }|	                    d          }|D ]>}|                    d          &t          j                            |
 d& d                                          }|j         dt,          |j                  d|j         }t0          j                            dd| gg dg ddd d|gd!d|gggd"#          }|r|                    d$          }|                    d%          }|r|                    |          }n|                    |          }d}t7          t9          &fd&|          d          }t7          t9          &fd'|          d          }|r	|d(         }n
|r|d)         }|r_t          j                            |
 d| d                                          }|d*|j         dt,          |j                  d|j         z  }d+|d,d-| j         }d.|| d/| d0|||j        |j        |                                |j        dz
  d1z  d2d3
}t0          j                            d4d5d6| gd7gd28          }|st0                              | 9          }|                                D ]\  }}tA          |||           |j!        rd"|_!        |j"        r|#                    d2:           |dk    r|$                    d;g           | $                    d<g           | j%        }|r|                                }n|                                }|j&        } t0          j'        (                    ||d7g=          }! || |!          }"|"D ]o\  }#}$|$rB|#st0          )                    |9          }#|$j*        |#_*        |$j+        |#_+        |$j,        |#_,        n	|#rd2|#_!        |#j"        r|##                    d2:           pn=|$                    d>g           |j&        D ]}#d2|#_!        |##                    d2:            |-                                 |.                                 |#                    d2:           |/                    |           @t0          j        (                    d4d?|gd4d5d+| d@gg dAgB          }%|%D ]}|0                    d2C           | $                    dDg           | 1                                 | #                    d2:           dS )Eu  
        Метод для кнопки "Загрузить праздники".
        Добавляет в исключения календаря выходные/празничные дни из файла в формате JSON.
        Исключения добавляются на текущий и следующий год (при наличии соответствующих файлов).
            - файлы с данными находятся в каталоге /opt/eva-app/cmf/contrib/calendar/
            - имя файла должено содержать только год (например, 2024.json)
        Добавления не происходит, если пользователь уже добавил исключение на такую же дату.
        При повторном выполнении метода происходит перезагрузка/восстановление/удаление,
        если поменялись данные в файле.
        r   )zip_longest)Pathu   Выходной деньu   Рабочий день)r   r   r   contribr?   z.jsonrzutf-8)encodingNr:   u   Год (uL   ) указанный в файле не соответствует году u    в имени файла c                 ,    i | ]}|d          |d         S )idr   r2   )r4   holidays     r(   
<dictcomp>z-CmfCalendar.load_holidays.<locals>.<dictcomp>  s"    ddd7wvdddr*   holidays	transfersr   r+   .z%Y.%m.%d r   r   )rt   r   rK   )rF   r   r   r   rp   rr   F)r   is_autor   r;  c                 6    |                      d          k    S )Nfromr   itemdate_s    r(   <lambda>z+CmfCalendar.load_holidays.<locals>.<lambda>.  s    TXXf=M=MQV=V r*   c                 6    |                      d          k    S )NtorD  rE  s    r(   rH  z+CmfCalendar.load_holidays.<locals>.<lambda>/  s    488D>>U;R r*   rJ  rC  u    за exclude_z%Y_%m_%d:rK   z ()r   T)
rt   r'  r   rp   rr   r3   r,   rv   ru   rA  r'  LIKE%r
   )r   r   include_deleted)r   r   r{   r   )r   r   r   r   zNOT INz_%)rA  r   Tr   )r   r   )2	itertoolsr4  pathlibr5  r%   r   r:   config
CMF_FOLDERstrwith_suffixexistsopenjsonloadsreadr   loggingwarningr   r+   r0   MONTHS_SHORTr3   re   r   r   nextr   r'  rN   r   r   cmf_deletedr   r   r   r   r   CmfCalendarWorkWeekIntervalr   CmfCalendarExcludeIntervalrV   rW   interval_minutes_calc_intervals_total_minutes_calc_exclude_typerI   delete_set_as_dirty)'r   r4  r5  	day_typesr   r   r:   	file_pathfcalendar_datacalendar_yearexclude_codesr=  r>  r   	date_datar+   date_strexcluder   r;  r   transfertransfer_fromtransfer_totransfer_dater'  exclude_datafieldrq   r   rN   exclude_intervalsworkweek_intervalsr   exclude_intervalworkweek_intervalexcludesrG  s'                                         @r(   r   zCmfCalendar.load_holidays  s    	*)))))       +(
 
	
  (,,..3|a/0 V	+ V	+DV.	:s4yyQQ]]^effI##%% iw777 51 $

16688 4 45 5 5 5 5 5 5 5 5 5 5 5 5 5 5 *--f55M}$$ !y= !y !yMQ!y !ymv!y !y z z zMddmFWFWXbFcFcdddH%))+66I!%%g..E" x+ x+	!f--(11]2L2LU2L2LjYY^^``"hOOdj)AOODIOO !388!3-/P/P/PRdRdRd 3S$?BSUXZ^A_` " 9     %==00#--	22 z#<<00DD$==22D#H$(0V0V0V0VXa)b)bdh$i$iM"&v.R.R.R.RT]'^'^`d"e"eK$ 7#0#6$ 7#.v#6 z(0(9(B(B,99x99:)G )GGKtvv & $y]->$y$ymNaAb$y$yerew$y$yy >$===$)== $/ #22x222)-'+!Z!%&*llnn,0HqLQ+>#    !377"FJJJ75$( 8  
  E$77t7DDG$0$6$6$8$8 3 3LE5GUE2222& 0*/G'% 1LL4L000q== ''888$$&8%9:::'+'<$$ 1"/"7"7"9"9"&,,..(/(9%)/)K)P)P/!( #u *Q * *& !,,=?Q R RI?H B B;(*;, @#3 e393T3T\c3T3d3d 09J9T,67H7P,4@Q@b,==- @;?,8+6 B,11D1AAAB ''666,3,= > >(7;(4(---====55777**,,,t,,,$$T**** 055=1!4D!4!4!45&&&>5  H
 $ + +T****+ 	'###		D	!!!!!s   !-CC	!C	c                 Z    d| _         t          j        j                                         dS )u   
        Помечает, что календарь необходимо пересчитать и сбрасывает кэш дней
        TN)r   re   r   r   r   r   s    r(   rh  zCmfCalendar._set_as_dirty  s)     
"..00000r*   c                 6   | j         j        sdS | j         st          dd           d| j         d}t          j                            dd| j        gg d	g
          }|r*d|_         |                    d           d|j         d| }t          |           dS )uX   
        Изменяет флаг "Календарь по умолчанию"
        Nu   Для сброса флага "Календарь по умолчанию" вы должны установить его у любого другого календаря.Tr\   uf   Во всех новых проектах будет использоваться календарь ""r:  r}   )
is_defaultr   TrQ  Fr   u^   Сбросили флаг "Календарь по умолчанию" у календаря "z". )	r  r   ra   r   re   r   r   r:  r   )r   msgcurrent_default_calendars      r(   _set_is_defaultzCmfCalendar._set_is_default  s    ) 	F 	G z BFG G G G Dw{  xA  D  D  D#)#5#9#9tTW%))) $: $
 $
  $ 	W27$/$))D)999V.F.KV VPSV VC#r*   c                 |    | j         j        rd| _         | j        j        rd| _        | j        j        r	d| _        dS dS )u}   
        Исправляет нулевые значения, если приходит null выставляет 0
        r   N)r   is_nullr   r   r~  s    r(   fix_null_valuezCmfCalendar.fix_null_value  sT     =  	DM& 	$"#D#+ 	)'(D$$$	) 	)r*   c                 0   | j         r}t          | dd           rt          j                            | j        g d          }|j        | _        |j        | _        |j        | _        |j	        D ]}|
                                }| |_        d|_        |                    d           |                    dg           |j        D ]3}|
                                }||_        |                    d           4|j        s|j        s|| _        |j        D ]}|
                                }	| |	_        |	                    d           |                    dg           |j        D ]3}|
                                }|	|_        |                    d           4nt(          j                                                                        j        }
t1          t(          j                            |
                                                                                    | _        t                              | d	          }t9          d
          D ]}t;          |d| dd           |                    d           || _        |                                  |                                  t1          | j                  dk    rtA          dd           t1          | j                  dk     rtA          dd           t1          | j                  dk    rtA          dd           t1          | j                  dk     rtA          dd           | j        j!        s| j        j!        r| "                                  tG                      j        |i |S )Ntemplate)r
   zworkweeks.*zexclusions.*)r:  r   FTr   r{   u   По умолчаниюr   r   r   r0   r   weekend   u\   Количество лет расчета не должно быть больше 30 летr\   r   u]   Количество лет расчета не должно быть меньше 1 годаr   un   Количество лет расчета в прошлое не должно быть больше 10 летr   uf   Количество лет расчета в прошлое не должно быть меньше 0)$is_newro   re   r   r   r  r   r   r   r   cloner   systemr   r   r   rp   rr   r   r   r%   r   
astimezoner   r#   	utcoffsetrf   r   r"   r   r  r  ra   r   rh  super)r   argskwargstemplate_calendartemplate_workweeknew_workweektemplate_intervalnew_intervaltemplate_exclusionnew_exclusionr   r   r'   	__class__s                r(   r   zCmfCalendar.save  s    ; /	1tZ.. .1$*$6$:$:}??? %; % %! !2 :&7&F#+<+P():)D = =%#4#:#:#<#<L*.L'*/L' %%%555%11=/BBB->-H : :)'8'>'>'@'@.:+$))D)9999'9 =,B^ =0<-*;*F 	: 	:&$6$<$<$>$>M+/M(!&&&666&22M?CCC-?-I : :)'8'>'>'@'@.;+$))D)9999:	: &**,,7799@ #H$5$9$9"$=$=$G$G$I$I$W$W$Y$Y Z Z "55THa5bbq A AAHnAnnni@@@@---(0%t"##b((t  }A  B  B  B  Bt"##a''u  ~B  C  C  C  Ct'((2--  G  OS  T  T  T  Tt'((1,,~  GK  L  L  L  L) 	!T-E-P 	!   uww|T,V,,,r*   c           	   #     K   d }d}|}t          g           }d }d}	d}
	 |sddd|gg dgg}|r6|	s|                    dd	|g           |r|
s|                    dd	|g           |                    t          j                            d
d	| g|gg ddgddg                     |s1t          d| j         d| j        	                                           |
                                }|j        |k    r0|j        dk    r# ||          }|j        |_        |j        |_        d}	|j        |k    rK|j        dk    r>|r|j        |_        |j        |_        n# ||          }|j        |_        |j        |_        d}
|j        dk    r|}|V  t                              |j        j        t          j                                                  t)          j        d          z                                   }|dz  })Nc                 z    t          t          |                     D ]}| |         j        dk    r
| |         c S d S )Nr   )r"   r  r   )bufferr'   s     r(   _get_next_work_dayz1CmfCalendar.work_days.<locals>._get_next_work_day  sP    3v;;'' % %!9%//!!9$$$ 0% %r*   r   FTr   r+   r   )r   ==r   r  r   )r+   r   r   r   zparent.timezoner   )r   r   order_byr-  u   Календарь "u   " рассчитан до r   r   rH   )r   rI   r   re   r   r   StopIterationr   r   loadpopleftr+   r   r   r   Datetimer_   rq   r   r`   r%   r&   )r   rC   r  finish_dater  n	next_dater  prev_work_daystart_date_givenfinish_date_givendate_filterr   next_work_days                 r(   r   zCmfCalendar.work_days
  s     	% 	% 	%
 	r !1	 #vtY&?A[A[A[%\]% H+ G#**FD*+EFFF" H+< H#**FD++FGGG)..!)4 6Dqqq"( "g	 /      z#$x49$x$x`d`o`t`t`v`v$x$xyyy~~''H}
**$..$6$6v$>$>M-:-HH*6C6ZH3#' }++$..$ _1>1L.:G:^77(:(:6(B(B1>1L.:G:^7$(! F** (NNN!))(-*=x|?P?P?R?RSSV^VhnoVpVpVppvvxxIFAc1	r*   c                 ^   |                      dg           | j        rt          dd           t          j                            | dg          }|r3d                    d |D                       }t          d	| d           t          j                            | 
          }|rt          d| dd           t          j	                            | ddg          }|r3d                    d |D                       }t          d| d            t                      j        |i |S )Nr  u   Нельзя удалить календарь по умолчанию. Сначала назначьте любой другой календарь, как календарь по умолчанию.Tr\   r   )r?   r   z, c                 $    g | ]}d |j          d S )r  )r   )r4   projects     r(   r6   z&CmfCalendar.delete.<locals>.<listcomp>T  s&    %R%R%Rg&9',&9&9&9%R%R%Rr*   uk   Невозможно удалить календарь, он используется в проектах: )r?   uY   Невозможно удалить календарь, он используется у u    пользователейzparent.namec                 >    g | ]}d |j         j         d|j         d S )r  z: r  )r4   slas     r(   r6   z&CmfCalendar.delete.<locals>.<listcomp>^  s2    &_&_&_c'I3:?'I'Ich'I'I'I&_&_&_r*   ue   Невозможно удалить календарь, он используется в целях: )r   r  ra   re   
CmfProjectr   join	CmfPersoncountCmfSDeskSlaGoalr  rg  )	r   r  r  projectsprojects_strperson_count	sla_goalssla_goals_strr  s	           r(   rg  zCmfCalendar.deleteK  s   ,(((? 	" Z " " " " $))4)II 	"99%R%R%R%R%RSSL  S  EQ  S  S " " " "'--t-<< 	"  \r~  \  \  \ " " " " *//v}F]/^^	 	" II&_&_U^&_&_&_``M  N  L  N  N " " " " uww~t.v...r*   )NNN)NNr   )NNr   N)NNr   NNN)NNNNNN)NN)NNT)F)N)NF)r   )5__name__
__module____qualname__r   r   api_methodsstaticmethodDater   r)   r#   rB   r   rK   rO   rR   rU   Timer   r  rb   rg   classmethodry   r   r   boolr   r   r	   rV  cmfr   CmfTyper   r   r   r   r   r
  r  r  r  cmf_deferred_jobr2  r   rh  r  r  r   r   rg  __classcell__)r  s   @r(   r   r      sT       *6:
 
K
 5 5 5$ 5 5 5 \5 CGOS D S 03ILX\   \< JN ! d d C &*4j   \B _`)-( (t (t (S (Y\ (!#Y(26t*( ( ( \(T LP6:=A1 1 1 1c 11031!17:1FJ4j1 1 1 \1f KO7;<@- -t -t -S --14- -69-EI$Z- - - \-^ 2604" "4 ")-"9>x?Q9R" " " \"6 : : :d :c : : : \:$ ?+F ?5QTVZ[_V`Q`Ka ? ? ? [?B K= K K K K K [KZ FJ04, ,= ,(,,?C,)-,9A, , , [,. +0
 
uS-9K%KL 

$(
59
 
 
 Y [
@ >BS  S ] S !#sD(":;S GOS  S  S  [S j," ,"# ,"T ,"h ," ," ," ,"\,( ,8 , , , , ,\ 05D DM DH DU] D:=D D D [DL @A2 2M 2&.2:=2FN2 2 2 [2h & & \&P 7 7 \7r LLL  ML \:p" p" p"d1 1 1  2) ) )B- B- B- B- B-H? ? ?B/ / / / / / / / /r*   r   )r?   r8   r%   	functoolsr   collectionsr   typingr   r   r   r   r	   pytzcmf.includecommon.fieldsr   dateutil.relativedeltar   r   r_  r`   r  r+   r  r  r   r2   r*   r(   <module>r     s*                    9 9 9 9 9 9 9 9 9 9 9 9 9 9      & & & & & & 4 4 4 4 4 4 4 4 HHHhH	  }}J/ J/ J/ J/ J/,* J/ J/ J/ J/ J/r*   