
    Ah             !          d Z ddlZddlZddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZmZmZ 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"m#Z# ddl$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/m0Z0m1Z1 ddl2m2Z2m3Z3 ddl4Z4ddl5Z5ddl6Z6ddl7Z7ddl8Z8ddl9m:Z:  ejv                  ejx                         ddl=m>Z? 	 ddl=m@Z@ ddl=Z=ddlBmCZCmDZDmEZEmFZFmGZG  e2j                         j                  d      ZJd ZKd ZLdZMdgZNd ZOdhdZPd ZQd ZRd ZSd  ZTd! ZUd" ZVd# ZWd$ ZXdid%ZYd& ZZd' Z[d( Z\d) Z]d* Z^d+ Z_d, Z`d- Zad. Zbd/ Zcd0 Zdd1 Zed2 Zfd3 Zgd4 Zhd5 Zid6 Zjd7 Zkd8 Zld9 Zmd: Znd; Zodhd<Zpdhd=Zqdhd>Zrd? Zsd@ ZtdA ZudB ZvdC ZwdD ZxdE ZydF ZzdG Z{dH Z|dI Z}dJ Z~dK ZdL ZdM ZdN ZdjdOZdP ZdQ ZdRedSedTefdUZddddddddddddVdWdXedYee   dZee   d[ee   d\ee   d]ee   d^ee   d_ee   d`ee   daee   dbee   dceeeef      ddedTeeee   ee   f   fdeZdkdfefdgZy# eA$ r dZ@Y dw xY w)lu  
Hier sind die alleinstehende Funktionen

Funktionen:
- send_msg_telegram: Asynchrones Senden einer HTML-formatierten Nachricht an IT Gruppe
- generate_uid: Generiert eine einzigartige UID

Imports:
- random: Für die Generierung zufälliger Nummern.
- string: Für den Zugriff auf Zeichensätze.
- telegram: Für die Interaktion mit der Telegram API.
- telegram.ext: Für die Verwaltung von Telegram Bots und deren Nachrichtenverarbeitung.

    N)service_account)build)MediaFileUpload)	HttpError)Credentials)InstalledAppFlow)secure_filename)OptionalDictTuple)geocoder)region_code_for_country_code)headers_tookanjson_api_key_tookanPLANDAY_CLIENT_IDPLANDAY_REFRESH_TOKENlara_bot_id	onduty_it)get_all_data_from_fk_tableget_value_by_idget_data_from_fk_tableget_all_user_idget_cred_google_service_accupdate_child_datainsert_child_datadelete_child_datainsert_partner_dbupdate_partner_datadelete_partner_dataget_doc_fk_tableinsert_docs_db)datetime	timedelta)ZoneInfo)level)__version__)__version_info__)r   r   r   r   r   )ApplicationCommandHandlerContextTypesMessageHandlerfilters%Y%m%dc                 r   K   t        j                  |       }|j                  ||d       d{    y7 w)u   
    Senden Nachrichten an Telegramuser/Grupper

    Args:
        TOKEN String: Token für Telegram Bot
        msg String: Nachricht 
        id int: id für den Chat
    HTML)
parse_modeN)telegramBotsend_message)TOKENmsgidbots       1/home/janakan/Projects/talentsphere/standalone.pysend_msg_telegramr9   9   s1      ,,u
C


2sv

666s   -757c                     t               } 	 t        j                  }t        j                  |      }|dt        t        j                  dd            dd z   j                  d      dd z   }dt        t        j                  dd	            dd z   j                  d
      dd }dt        t        j                  dd	            dd z   j                  d
      dd }d|z   |z   |z   }|| vr| j                  |       |S )u   
    Generiert eine UID mit der vorgegebenen Vorlage.

    Dies wurde von der Gastro Kurier GmbH vorgegeben.

    Returns:
        str: rückgabe einer UID
    0000000r   i   N   i0000i     UID)	r   stringascii_uppercaserandomchoicehexrandintzfilladd)existing_uidsletters
first_charpart1part2part3	hex_values          r8   generate_uidrQ   G   s     $%M
((]]7+
i#fnnQ	.J*KAB*OOVVWXYZ\Z]^^#fnnQ56qr::AA!DRSI#fnnQ56qr::AA!DRSIEME)E1	M)i(     zgoogle/credentials.jsonz%https://www.googleapis.com/auth/drivec                    	 | j                         j                  |dd      j                         }y# t        $ rO}t	        j
                  d|       |j                  j                  dk(  rt	        j
                  d|       Y d }~yd }~ww xY w)Nr6   TfileIdfieldssupportsAllDriveszAn error occurred: %si  zFolder with ID %s not found.F)filesgetexecuter   loggingerrorrespstatus)service	folder_idfolderr\   s       r8   check_folder_existsrb   e   st    $$IdUY$Zbbd -u5::#MM8)D	s   03 	BABBc                 J   t               }	 t        j                  j                  |t              }	 t        dd|      }t        ||      sd
| dS | d|r|gng d}	 |j                         j                  |dd      j                         }|j                  d      }|S # t
        $ r }t        j                  d|       Y d}~yd}~ww xY w# t
        $ r }t        j                  d|       Y d}~y	d}~ww xY w# t        $ r }	t        j                  d|	       Y d}	~	yd}	~	ww xY w)an  
    Creates a folder in Google Drive with the given name, within a specified parent folder.

    Args:
        folder_name (str): The name of the folder to create.
        parent_id (str): The ID of the parent folder in which to create the new folder (default is set).

    Returns:
        str: The URL of the created folder, or a message indicating an error.
    scopes.Failed to load service account credentials: %sN*Error loading service account credentials.drivev3credentials%Failed to build the Drive service: %s!Error building the Drive service.zParent folder with ID z not found or inaccessible.z"application/vnd.google-apps.folder)namemimeTypeparentsid, webViewLinkT)bodyrV   rW   webViewLinkz,An error occurred during folder creation: %szError creating the folder.)r   r   r   from_service_account_infoSCOPES	Exceptionr[   r\   r   rb   rX   createrZ   rY   r   )
folder_name	parent_id	cred_jsonrk   er_   file_metadatafile
folder_urlr\   s
             r8   create_google_folderr   p   s$    ,-I<%11KKf L &3;? w	2'	{2MNN 8"+I;M,}}%%=ARfj%kssuXXm,
5  <FJ;<  3=qA23(  ,DeL+,sG   %B! C AC9 !	C
*CC
	C6C11C69	D"DD"c           
      L   || |d||ddd}|j                  t               t        j                  d|t              }|j                         }|d   dk(  r|j                  dk(  r|d	   d
   S t        j                  t        t        d| d|j                   dt                     y)z_summary_

    Args:
        phone (_type_): Phonenumber
        username (_type_): username
        firstname (_type_): firstname
        lastname (_type_): lastname
        email (_type_): email

    Returns:
        fleet_id int: Id of Tookan 
    z0800!Dinner1558633z-120)emailphoneusernamepassword
first_name	last_nameteam_idtimezonez&https://api.tookanapp.com/v2/add_agentjsonheadersr^      datafleet_idzTookan acc creation fail of :  | TalentsphereN)updater   requestspostr   r   status_codeasynciorunr9   r   textr   )r   r   	firstnamelastnamer   valresponser   s           r8   create_tookan_accr      s     !	C JJ"#}}ECYghH==?DH~3&<
++KK!+1MeWTVW_WdWdVeet/u  xA  B  CrR   c                     | ||d}|j                  t               t        j                  d|t              }|j                         }|d   dk(  ryy)a  
    Blocks or unblocks a user in the Tookan system based on the provided fleet ID and block status.

    Args:
        fleet_id (int): The unique identifier for the Tookan user to be blocked or unblocked.
        block_status (int): The status indicating whether to block (1) or unblock (0) the user. Default is 1 (block).

    Returns:
        dict: The response data from the Tookan API, indicating success or failure of the operation.
    )r   block_statusblock_reasonz4https://api.tookanapp.com/v2/block_and_unblock_fleetr   r^   r   TF)r   r   r   r   r   r   )r   r   reasonr   r   r   s         r8   update_tookan_user_statusr      sU     $C
 JJ"#}}SZ]guvH==?DH~rR   c                     t        t              }t        j                  d|  |      }|j	                         }|j
                  dk(  r|d   d   r|d   d   S y)a  
    Retrieves the department name of a Planday employee using their unique Planday ID.

    This function sends a GET request to the Planday API to fetch employee details based on the provided
    Planday ID. It extracts and returns the department name from the response data.

    Parameters:
    - planday_id (int): The unique identifier of the employee in the Planday system.

    Returns:
    - str or None: The name of the department associated with the employee if found; otherwise, None.

    Raises:
    - requests.exceptions.RequestException: An error from the `requests` library if the HTTP request fails.
    - KeyError: If the expected 'departmentNames' key is not found in the JSON response.

    Note:
    - Ensure that the Planday API credentials and client ID are correctly configured in the environment.
    - The function assumes that the employee has at least one department assigned; if not, it returns None.
    .https://openapi.planday.com/hr/v1.0/employees/)r   r   r   employeeGroupsN)get_planday_tokanr   r   rY   r   r   )
planday_idplanday_headersr   r   s       r8   get_user_planday_employeeGroupsr      sa    * ((9:O||LZLYcrsH==?Ds"<()< 011rR   c                 \   t        t              }d|  }|yt        j                  |      }d|d   i}	 t	        j
                  |i |ddi|      }t        |j                  |j                         |j                  dk(  ryy	# t        j                  $ r}t        d
|         d}~ww xY w)a  
    Reactivates a Planday employee account and assigns them to specified employeeGroups.

    This function sends a PUT request to the Planday API to reactivate an employee account identified by
    the provided Planday ID. It also assigns the employee to the given list of department IDs.

    Parameters:
    - planday_id (int): The unique identifier of the employee in the Planday system.
    - employeeGroups (list of int): A list of department IDs to which the employee will be assigned upon reactivation.

    Returns:
    - bool: True if the reactivation and assignment were successful (HTTP status code 200), False otherwise.

    Raises:
    - requests.exceptions.RequestException: An error from the `requests` library if the HTTP request fails.

    Note:
    - Ensure that the Planday API credentials and client ID are correctly configured in the environment.
    - The function assumes that the provided department IDs are valid and exist in the Planday system.
    r   NTr   Content-Typeapplication/jsonr   r      FRequest failed: )
r   r   r   loadsr   putprintr   r   RequestException)r   r   r   urlpayloadr   r{   s          r8   +move_planday_acc_from_DNF_to_employeeGroupsr      s    * ((9:O::,
GCZZ/N.)9:G<<KK8JK

 	h""HMM23&$$  $%s   AB B+B&&B+c                     | ddd}t        j                  d|      }t        j                  |j                        }|d   }d|z  | d}|S )	a  
    Obtain authorization headers for accessing the Planday API by using a refresh token to request a new access token.

    This function sends a POST request to the Planday authentication endpoint with the client ID, grant type,
    and a predefined refresh token. It extracts the access token from the response and prepares an authorization
    header that can be used in subsequent API requests to Planday.

    Parameters:
    - client_id (str): The client ID associated with the Planday API credentials.

    Returns:
    - dict: A dictionary containing the headers necessary for authenticated API requests. This includes:
        - 'Authorization': A bearer token format of the access token.
        - 'X-ClientId': The client ID passed to the function.

    Raises:
    - requests.exceptions.RequestException: An error from the `requests` library if the HTTP request fails.
    - KeyError: If the expected 'access_token' is not found in the JSON response.

    Note:
    - The refresh token is hardcoded in the function, which is not recommended for production code.
      It's advisable to secure such tokens and make them configurable through secure means.
    refresh_tokenUZnpFHpVpkKmkBx6y4AG5A)	client_id
grant_typer   z$https://id.planday.com/connect/tokenr   access_tokenz	Bearer %s)Authorizationz
X-ClientId)r   r   r   r   r   )r   data_plandayresponse_planday	json_resptoken_plandayr   s         r8   r   r   (  s^    0 "+/\tvL}}%KR^_

+001In-M %}4G NrR   c                     | j                         d   }t        d      }|D ]$  }|d   j                         d   }||k(  s|d   c S  y)aY  
    Retrieve the department ID for a given department name from a list of departments.

    This function splits the provided department name to extract the last word, which is assumed to
    represent a unique department code. It then queries a fictional database or data structure
    containing department details and searches for a matching department code within these details
    to return the corresponding department ID.

    Parameters:
    - department_name (str): The full name of the department, where the last word is expected to be a unique code.

    Returns:
    - int or None: The unique department ID corresponding to the given department name. Returns None if no
      matching department is found.

    Raises:
    - KeyError: If 'department_name' key is not found in any item of the planday_departments list.

    Example Usage:
    >>> get_department_id("Finance Department A123")
    101  # Assuming '101' is the ID associated with "Finance Department A123"

    Note:
    - The function `get_all_data_from_fk_table` is assumed to be a pre-existing function that retrieves
      all department data from a source such as a database or a flat file. The structure of the returned
      data is assumed to be a list of dictionaries, where each dictionary represents a department with
      keys including 'department_name' and 'department_id'.
    planday_departmentsdepartment_namedepartment_idNsplitr   )r   ou_code_inputr   itemou_code_plandays        r8   get_department_idr   M  s^    : $))+B/M45JK# )01779"=m+(()rR   c                     | j                         d   }t        d      }|D ]$  }|d   j                         d   }||k(  s|d   c S  y)a  
    Retrieves the unique group ID associated with a specified department name from a predefined list of employee groups.

    This function extracts the last word from the given department name, treating it as a unique identifier code.
    It then searches through a dataset of employee groups for a matching code. If a match is found, the associated
    group ID is returned.

    Parameters:
    - department_name (str): The name of the department, where the last word is expected to be the unique identifier code.

    Returns:
    - int or None: The group ID associated with the provided department name if found, otherwise None.

    Raises:
    - KeyError: If 'name' or 'groupId' keys are missing in any of the dictionaries within the list returned by
      `get_all_data_from_fk_table`.

    Note:
    - The `get_all_data_from_fk_table` function is assumed to access a database or some form of persistent storage
      to retrieve a list of dictionaries, each representing an employee group with at least 'name' and 'groupId' keys.
    - This function does not handle multiple matches; it returns the group ID for the first match found.

    Example Usage:
    >>> get_employee_group_id("Sales Division X200")
    500  # Assuming '500' is the group ID for "Sales Division X200"

    This function is particularly useful for systems where department or group identifiers are embedded in
    descriptive text fields and consistency in naming conventions is maintained.
    r   planday_employee_grouprn   groupIdNr   )r   r   planday_groupsr   r   s        r8   get_employee_group_idr   q  s_    < $))+B/M/0HIN #v,,,.r2m+	?"# rR   c                    | d| d| d|
 }t        |	      }t        t              }t        dd|d      }t	        |      }t        ||       t        |      }t        |      }| |||g|g||dd ||||d}t        j                  d	||
      }|j                         }t        |       |j                  dk(  r|d   d   S t        j                  t        t        d|j                    dt"                     y)a  
    Creates a new employee account on the Planday platform using provided personal and departmental details.

    This function compiles an employee's information into a formatted request to the Planday API. It handles the
    assembly of complex objects like addresses and phone numbers, resolves department and group IDs, and posts
    this data to the Planday HR endpoint to create an employee record.

    Parameters:
    - firstname (str): The first name of the employee.
    - lastname (str): The last name of the employee.
    - email (str): The email address of the employee.
    - department (str): The department ID used to fetch the department's name and unique identifier.
    - phone (str): The employee's cell phone number.
    - prefix (str): The dial code prefix for the employee's phone number.
    - address (str): The street address of the employee.
    - postal_code (str): The postal code of the employee's address.
    - location (str): The city or locality of the employee's address.
    - salutation_id (int): The ID used to fetch and determine the salutation and gender of the employee.
    - country (str): The country of the employee's address.
    - birthDate (str): The birth date of the employee in ISO 8601 format (YYYY-MM-DD).

    Returns:
    - int or None: Returns the unique Planday employee ID if the creation is successful, otherwise None.

    Raises:
    - requests.exceptions.RequestException: If the HTTP request to the Planday API fails.
    - KeyError: If expected keys are missing from the API response.

    Example Usage:
    >>> create_planday_acc("John", "Doe", "john.doe@example.com", "5", "555-1234", "+1", "123 Elm St",
                           "90210", "Springfield", 1, "USA", "1980-01-01")
    12345  # Assuming the Planday API returns an employee ID of 12345

    Note:
    - This function directly interacts with external services (Planday API) and expects certain helper functions
      to provide necessary preprocessing of input data (e.g., `get_department_id`, `get_gender`).
    - The function assumes that all helper functions are implemented correctly and available in the scope.
    - It is assumed that the API keys and client IDs are correctly configured in the environment.
    ,  users_regionr6   region   N)	firstNamelastNameuserNamedepartmentsr   	cellPhonecellPhoneCountryIdcellPhoneCountryCodegenderstreet1	birthDatez-https://openapi.planday.com/hr/v1.0/employeesr   r   r   zPlanday acc creation fail: r   )
get_genderr   r   r   r   r   r   get_country_code_by_dial_coder   r   r   r   r   r   r9   r   r   r   )r   r   r   
departmentr   prefixaddresspostal_codelocation	gender_idcountryr   r   r   r   r   r   employeeGroup_idphone_country_coder   r   r   s                         r8   create_planday_accr     s   P 	K=(2gY?G	"F'(9:O%nd:xPO%o6M	/=),_=6v>%+,$QRj 2
C }}LVelopH==?D	$Ks"F|D!!KK!+1LX]]O[j/kmvwxrR   c                 T   |#t        j                         j                  d      }t        t              }|||d}d|  }	 t        j                  |||      }t        |j                  |j                         |j                  dk(  ryy# t
        j                  $ r
}Y d}~yd}~ww xY w)	a  
    Deactivates an employee account on the Planday platform using the provided employee ID.
    
    Sends a PUT request with required JSON payload.

    Parameters:
    - employee_id (int): The employee's ID.
    - reason (str): Reason for deactivation.
    - keep_shifts (bool): Whether to keep the employee's existing shifts.
    - date (str): Deactivation date in YYYY-MM-DD format. Defaults to today.

    Returns:
    - bool: True if successful (204), False otherwise.
    Nz%Y-%m-%d)dater   
keepShiftsz9https://openapi.planday.com/hr/v1.0/employees/deactivate/r   r   TF)r"   todaystrftimer   r   r   r   r   r   r   r   )	employee_idr   keep_shiftsr   r   r   r   r   r{   s	            r8   deactivate_planday_accr     s      |~~((4'(9:O!G Fk]
SC	<<_7Kh""HMM23&$$ s   AB
 
B'"B'c                    t        t              }ddgi}d|  }	 t        j                  |||      }t	        |j
                  |j                         |j
                  dk(  ryy# t        j                  $ r
}Y d }~yd }~ww xY w)Nr   ih) r   r   r   TF)r   r   r   r   r   r   r   r   )r   r   r   r   r   r{   s         r8   move_planday_acc_DNFr     s    '(9:O6(G ;;-
HC	<<_7Kh""HMM23&$$ s   AA$ $B<Bc                    t        dd|d      }t        |      }t        t              }d|  }d|gi}	 t	        j
                  |i |ddi|      }|j                  d	k(  ry
y# t        j                  $ r}t        d|         d}~ww xY w)ak  
    Reactivates an employee account on the Planday platform using the provided employee ID
    and assigns them to the given department.
    
    Parameters:
    - employee_id (int): The employee's ID.
    - department (int): The internal region ID which maps to a Planday department.
    
    Returns:
    - bool: True if successful (204), False otherwise.
    r   r6   r   z9https://openapi.planday.com/hr/v1.0/employees/reactivate/r   r   r   r   r   TFr   N)	r   r   r   r   r   r   r   r   r   )	r   r   r   r   r   r   r   r   r{   s	            r8   reactivate_planday_accr     s     &ndJQO%o6M'(9:OEk]
SC 	G<<KK8JK

 3&$$  $%s   -A# #B
6BB
c                     i } g }t         j                  D ]c  }t        |dd       }|st        j                  |      }|dkD  s.d| }| j                  ||       |j                  |j                  ||d       e | j                  dd       t        d |D              s|j                  dddd       t        | j                         d	 
      D cg c]
  \  }}||d }}}|j                  d 
       ||fS c c}}w )Nalpha_2r   +)r   region_code	dial_codez+383XKc              3   ,   K   | ]  }|d    dk(    yw)r   r   N ).0xs     r8   	<genexpr>z,get_country_dialing_codes.<locals>.<genexpr>S  s     ;Aq4';s   Kosovoc                 $    t        | d   dd        S )Nr   r   )intr  s    r8   <lambda>z+get_country_dialing_codes.<locals>.<lambda>Y  s    s1Q48} rR   )key)r   r   c                     | d   S )Nr   r  r	  s    r8   r
  z+get_country_dialing_codes.<locals>.<lambda>[  s
    9 rR   )	pycountry	countriesgetattrphonenumberscountry_code_for_region
setdefaultappendrn   anysorteditemssort)	unique_codesr  crcccdialdrcountry_dial_codes_uniques	            r8   get_country_dialing_codesr   B  s   LI   	XQ	4(11"56rd8D##D"-QUVW	X FD);;;XdQWXY
 <--/5LM!Aq *! ! NN-N.$i//!s   C<c                     | j                  d      sd| z   } 	 t        | dd       }t        |      }|S # t        $ r
}Y d}~yd}~ww xY w)a  
    Converts a telephone dialing code to its corresponding ISO country code.

    This function normalizes the input dial code to ensure it starts with a '+', and then attempts to map this
    dialing code to a country's ISO code using the country's numeric code. The conversion relies on parsing
    the dial code into a numeric country code and then finding the corresponding ISO country code.

    Parameters:
    - dial_code (str): The international telephone dialing prefix for a country, which may or may not start with '+'.

    Returns:
    - str or None: The ISO country code associated with the given dial code if found; otherwise, None.

    Raises:
    - ValueError: If the conversion of the dial code to an integer fails or if the `region_code_for_country_code` does not
      recognize the numeric country code.

    Example Usage:
    >>> get_country_code_by_dial_code('+1')
    'US'  # Assuming '+1' corresponds to the United States

    Notes:
    - The function prints an error message and returns None if it encounters an error during the process,
      including improper input formats or unrecognized dial codes.
    - It assumes the presence of a `region_code_for_country_code` function or similar utility that maps numeric
      country codes to their ISO country codes.

    This function is particularly useful for applications that need to associate telephone numbers with countries,
    such as in telecommunications software or geographic data processing.
    r   r   N)
startswithr  r   rv   )r   country_coder   r{   s       r8   r   r   _  sT    B $)O	9QR=)2<@ s   3 	AAc                     d| k(  ryy)a  
    Determines the gender based on the provided salutation.

    This function compares the given salutation to common gender-specific titles used in French ("Monsieur") and
    German ("Herr") to determine the gender. If the salutation matches these male titles, it returns 'Male'.
    Otherwise, it assumes the gender is 'Female'.

    Parameters:
    - salutation (str): The salutation or title used to address an individual, typically indicating their gender.

    Returns:
    - str: 'Male' if the salutation indicates a male individual, 'Female' otherwise.

    Example Usage:
    >>> get_gender("Monsieur")
    'Male'
    >>> get_gender("Frau")
    'Female'

    Note:
    - This function currently supports a limited range of salutations and assumes any non-specified salutations are female.
      Additional salutations can be added to expand coverage or improve accuracy.
    - The function is designed with specific cultural norms in mind and might not be applicable globally without modifications.
    r   MaleFemaler  )
salutations    r8   r   r     s    4 	JrR   c                    d }| d   D ]  }| d   j                  d      }| d   j                  d      } |||         } |||         }i }i }t        |j                               j                  |j                               }	|	D ]4  }
|j	                  |
      }|j	                  |
      }||k7  s+|||
<   |||
<   6 |r|ni ||<   |r|ni ||<    | S )a  
    Analyzes changes between 'content_old' and 'content_new' within the provided data dictionary,
    identifying differences in dictionary-formatted string values.

    This function processes a list of rows contained in `data_dict`. Each row is expected to have
    two entries: 'content_old' and 'content_new', which are strings that represent dictionaries.
    It compares these dictionary entries for each row, extracts differences, and updates the rows
    with dictionaries reflecting changes in values for overlapping keys.

    Parameters:
    - data_dict (dict): A dictionary containing keys 'data' and 'columns'.
        'data' should be a list of lists where each inner list represents a row of data.
        'columns' should be a list of column names indicating where 'content_old' and 'content_new' are positioned.

    Returns:
    - dict: The same `data_dict` input dictionary where the 'content_old' and 'content_new' fields in each row
            have been replaced with dictionaries only containing keys that had different values between old and new.

    Raises:
    - IndexError: If 'content_old' or 'content_new' are not found in the 'columns' list.
    - SyntaxError, ValueError: These exceptions may be raised and caught within the helper function `load_dict`
      due to improper formatting or content in the string representations of the dictionaries.

    Example Usage:
    >>> data = {
        'columns': ['id', 'content_old', 'content_new'],
        'data': [
            [1, "{ 'a': 1, 'b': 2 }", "{ 'a': 1, 'b': 3 }"]
        ]
    }
    >>> compare_detailed_changes(data)
    {'columns': ['id', 'content_old', 'content_new'], 'data': [[1, {}, {'b': 3}]]}

    Note:
    - The function assumes the string representations in 'content_old' and 'content_new' are valid Python
      dictionaries expressed as strings. Errors in formatting can lead to empty dictionaries being processed.
    c                     	 t        j                  |       S # t        $ r}i cY d }~S d }~wt        $ r}i cY d }~S d }~ww xY wNastliteral_evalSyntaxError
ValueError)r   r{   s     r8   	load_dictz:compare_detailed_changes_administration.<locals>.load_dict  s;    	##D)) 	I 	I	s     	?'??:??r   columnscontent_oldcontent_new)indexsetkeysintersectionrY   )	data_dictr0  row	index_old	index_newr2  r3  differences_olddifferences_newcommon_keysr  	old_value	new_values                r8   'compare_detailed_changes_administrationrA    s   N   Di(..}=	i(..}=	I/I/+**,-::;;K;K;MN 	1C#,I#,II%'0$'0$	1 -<I,;I'D* rR   c                    d }| D ]  }|j                  dg       D ]
  } ||        |j                  dg       D ]r  }d}d|d   v r|d   d   } ||       d|d<   d|d	<   |d
   }|dk(  r
d|d<   ||d<   |dk7  r|d   j                  dd       |dk(  rd|d	<   |d   j                  d
d       t |j                  dg       D ]  }|d   j                  d      xs |d   j                  d      }|d   j                  dd        ||       |d
   dk(  rd|d<   n|d
   dk(  rd|d	<   n
d|d<   d|d	<   |r||d<   |d   j                  d
d        |j                  dg       D ]  }	|	d   j                  d      xs |	d   j                  d      |	d<    ||	       |	d   s|	d   rd|	d<   n|	d   s|	d   rd|	d	<   n
d|	d<   d|	d	<   |	d
   dk(  rd|	d<   n|	d
   dk(  rd|	d	<   n
d|	d<   d|	d	<   |	d   j                  d
d         | S )aY  
    Compares old and new content in a list of user data entries to identify changes, additions, and deletions,
    and marks each entry accordingly.

    Process:
        - Defines a helper function `compare_content` that:
            - Compares `content_old` and `content_new` for each row to identify differences.
            - Stores differences in separate dictionaries, `differences_old` and `differences_new`.
            - Flags new keys in `content_new` that do not exist in `content_old`.
            - Extracts the `operation` field to track whether the entry was inserted, updated, or deleted.
        - Iterates through each user, partner, children, and file data entry in `data_list`:
            - Compares the content of each entry and marks entries as added or deleted based on the operation.
            - Adds metadata like deletion reasons, identifiers, and other relevant attributes.

    Args:
        - data_list (list of dicts): List of dictionaries where each dictionary contains user data, including old and new
          content, operations, and other metadata.

    Returns:
        - list of dicts: The modified `data_list` with additional keys indicating added or deleted entries,
          and populated with old/new content differences.

    Example Usage:
        - Use this function to track changes across user data, showing added, deleted, or modified records.

    Notes:
        - Assumes data entries contain fields like `content_old`, `content_new`, and `operation` for comparisons.
        - Handles cases where some data fields are missing or added between old and new content.

    c                 
   | j                  di       }| j                  di       }i }i }t        |j                               j                  |j                               j	                  dh      }|D ]4  }|j                  |      }|j                  |      }||k7  s+|||<   |||<   6 |j                  d|j                  dd            }	|	| d<   |r|ni | d<   |r|ni | d<   |j                         D ]  }||vs||   ||<    | S )a  
        Compares `content_old` and `content_new` within a row to identify field changes, tracking differences
        and storing them in `differences_old` and `differences_new`.

        Process:
            - Retrieves shared keys between old and new content, ignoring `operation`.
            - Checks for differences in each common key and stores them.
            - Flags any new keys in `content_new` that are not in `content_old`.
            - Extracts `operation` from either old or new content.

        Args:
            - row (dict): A dictionary containing `content_old` and `content_new`.

        Returns:
            - dict: The modified row with differences and operation flags.
        r2  r3  	operationN)rY   r5  r6  r7  
difference)
r9  r2  r3  r<  r=  r>  r  r?  r@  rD  s
             r8   compare_contentz7compare_detailed_changes_users.<locals>.compare_content  s*   & ggmR0ggmR0 +**,-::;;K;K;MNYY[fZgh  	1C#,I#,II%'0$'0$	1  OOKd1ST	$K0?_RM0?_RM ##% 	8C+%'23'7$	8 
rR   	user_datapartner_dataNpartner_delete_reasonr3  FdeletedaddedrD  deleteTdelete_reasoninsertchildren_datar2  r   UserId
identifier
files_datarn   )rY   pop)
	data_listrF  entryuser_rowpartner_rowrM  rD  	child_rowr   	files_rows
             r8   compare_detailed_changes_usersrZ    s   @.b  H<		+r2 	&HH%	& !99^R8 	>K M&+m*DD +M :;R SK( &+K	"#(K  $K0IH$)-I&/<O,H$M*../FMH$'+G$&**;='	>, ?B7 	<I"=155lCqyQ^G_GcGcdpGqJm$((48I& %1'+	)$;'83%)	'"',	)$%*	'" *4	,'m$((d;%	<* <4 	<I )- 8 < <V D l	R_H`HdHdekHlIfI& ]+	-0H'+	)$}-)M2J%)	'"',	)$%*	'" %1'+	)$;'83%)	'"',	)$%*	'"m$((d;/	<cH<V rR   c                 \    	 t        j                  |       S # t        t        f$ r i cY S w xY w)z;Helper function to parse content strings into dictionaries.r+  )content_strs    r8   parse_contentr]    s1    ,,$ 	s    ++c                    g }| D ]  }|d   |d   |d   |d   t        |d   t              r|d   j                  d      n|d   g g g g d	}t        |d         }t        |d	         }t	        |      }t	        |      }|j                  d
g       }|j                  d
g       }|j                  dg       }|j                  dg       }	|j                  dg       }
|j                  dg       }|j                  dg       }|j                  dg       }t        ||      D ]  \  }}|d
   j                  ||d        t        |      t        |      kD  r+|t        |      d D ]  }|d
   j                  i |d        nAt        |      t        |      kD  r*|t        |      d D ]  }|d
   j                  |i d        t        ||	      D ]  \  }}|d   j                  ||d        t        |	      t        |      kD  r+|	t        |      d D ]  }|d   j                  i |d        nAt        |      t        |	      kD  r*|t        |	      d D ]  }|d   j                  |i d        t        |
|      D ]  \  }}|d   j                  ||d        t        |      t        |
      kD  r+|t        |
      d D ]  }|d   j                  i |d        nAt        |
      t        |      kD  r*|
t        |      d D ]  }|d   j                  |i d        t        ||      D ]  \  }}|d   j                  ||d        t        |      t        |      kD  r+|t        |      d D ]  }|d   j                  i |d        nAt        |      t        |      kD  r*|t        |      d D ]  }|d   j                  |i d        |j                  |        |S )at  
    Converts a list of raw database rows into a structured format with user, partner, children, and file data,
    tracking changes between old and new content.

    Process:
        - Initializes a structured dictionary for each row, parsing details such as user ID, editor, timestamp, and operation.
        - Separates each row's old and new content for user, partner, children, and file data.
        - Handles cases where data has been added or removed, ensuring unmatched items are included.
        - Appends the processed structure to a list representing all data.

    Args:
        - rows (list of lists): A list of rows, where each row contains:
            - id (int): Primary identifier.
            - user_id (str): User identifier.
            - content_old (str): Old content state (to be parsed).
            - content_new (str): New content state (to be parsed).
            - func (str): Operation performed.
            - editor (str): Username of the editor who made changes.
            - ts (datetime or str): Timestamp of the change, converted to a formatted string if datetime.

    Returns:
        - list of dicts: Each dictionary represents a row, structured with changes tracked for each type of data.

    Example Usage:
        - Convert raw rows to a structured format for use in tracking change history, showing additions, updates, or deletions.

    Notes:
        - Assumes helper functions `parse_content` to parse JSON-like content and `convert_to_user_data_format` for content formatting.
        - Handles changes for each data category separately, accommodating different lengths in old vs. new data lists.
    r   r      r?      z%Y-%m-%d %H:%M:%S)	r6   user_idfunceditortsrG  rH  rO  rR  r<      rG  rH  rO  rR  )r2  r3  N)	
isinstancer"   r   r]  convert_to_user_data_formatrY   zipr  len)rowsall_datar9  row_datar2  r3  old_user_datanew_user_dataold_partner_datanew_partner_dataold_children_datanew_children_dataold_files_datanew_files_data	old_entry	new_entrys                   r8   convert_raw_rows_to_structurerw    s   @ H  u" a&1vF!f:DSVX:V#a&//"56\_`a\b

 $CF+#CF+ 2+>1+> $R8#R8&??>2>&??>2>'OOOR@'OOOR@$r:$r: %(}$E 	 Iy[!((((* 	 }M 22*3}+=+>? 	%,,#%#,. 
 #m"44*3}+=+>? 	%,,#,#%.  %((8:J$K 	 Iy^$++((- 	  3'7#88-c2B.C.DE 	(//#%#,1 
 !"S)9%::-c2B.C.DE 	(//#,#%1  %((9;L$M 	 Iy_%,,((. 	  !C(9$::.s3D/E/FG 	)00#%#,2 
 "#c*;&<<.s3D/E/FG 	)00#,#%2  %($G 	 Iy\"))((+ 	 ~^!44+C,?,@A 	&--#%#,/ 
  3~#66+C,?,@A 	&--#,#%/  	!ku"n OrR   c                 l    t        | t              rt        j                  |       }n| }d| vr|gg g dS |S )a  
    Converts a flat dictionary to a structure with 'user_data', 'children_data', and 'partner_data'
    if they don't already exist. If they already exist, it leaves the structure unchanged.
    
    If 'user_data', 'children_data', or 'partner_data' fields are missing, it wraps the original data 
    into 'user_data' and sets 'children_data' and 'partner_data' as empty lists.
    rG  )rG  rO  rH  rf  strr,  r-  )	json_datajson_data_defs     r8   rg  rg  6  sJ     )S!((3!)# (
 	
 rR   c                     g d}|D ]a  }|dk(  rd}n|}|dk(  rt        ddd      }nt        d|z   dd      }|D ]-  }|| v st        | |         t        |d	         k(  s&|d
   | |<   / c | S )a  
    Replaces foreign key identifiers in a given dictionary with their corresponding descriptive data from
    database tables.

    This function examines specified keys in the input dictionary that are considered foreign keys. For each
    foreign key, it fetches the actual descriptive data from a database table using a helper function
    `get_data_from_fk_table`. It then replaces the numeric foreign key ID in the dictionary with the fetched
    descriptive data.

    Parameters:
    - data (dict): A dictionary containing key-value pairs, where the keys include foreign key references that
                   need to be resolved to more descriptive data.

    Returns:
    - dict: The modified dictionary where foreign key values have been replaced with their corresponding
            descriptive data.

    Raises:
    - KeyError: If a key expected to contain a foreign key ID does not exist in the dictionary.
    - ValueError: If there are issues converting data types during comparisons or data fetching.

    Example Usage:
    >>> data_dict = {
        'approval': '1',
        'civil_status': '2',
        'fleet': '3'
    }
    >>> updated_data = get_fk_data_from_dict(data_dict)
    >>> print(updated_data)
    {'approval': 'Approved', 'civil_status': 'Married', 'fleet': 'FleetName'}

    Notes:
    - The `get_data_from_fk_table` function is assumed to return a list of dictionaries, each containing an 'id' and a
      'description' key. The function uses these keys to replace the foreign key IDs in the `data` dictionary.
    - This function only updates the dictionary if the foreign key is present and a match is found; otherwise, the
      original values remain unchanged.
    - The table name is generated by prefixing 'users_' to the key, except for 'salutation', which maps to 'salution' in
      the database.

    This function is useful for resolving foreign keys to human-readable information before presenting data in
    user interfaces or reports.
    )
approvalcivil_statusfleetr   r'  status_applicationstatus_worktypr   partner_approvalr'  salutionr  users_approvalr   Tusers_r6   description)r   rz  )r   key_listr  key_db	real_datareals         r8   get_fk_data_from_dictr  O  s    Z WH   ,FF$$./?DII.x&/@!TJI  	Dd{tCy>Sd_4 $] 3DI		( KrR   c                 j   | D ]  }|j                  dg       D ]$  }t        |d         |d<   t        |d         |d<   & |j                  dg       D ]$  }t        |d         |d<   t        |d         |d<   & |j                  dg       D ]$  }t        |d         |d<   t        |d         |d<   &  | S )a  
    Processes a list of dictionaries containing 'user_data' and 'children_data', replacing foreign key identifiers
    in 'content_old' and 'content_new' with descriptive data using get_fk_data_from_dict.
    
    Args:
    - data_list (list): A list of dictionaries, where each dictionary contains 'user_data' and 'children_data',
      and each data item has 'content_old' and 'content_new' keys.

    Returns:
    - list: The processed list with foreign key IDs replaced by descriptive data in 'content_old' and 'content_new'.
    rG  r2  r3  rH  rO  )rY   r  )rT  rU  rV  rX  s       r8   process_fk_data_for_listr    s      W		+r2 	UH&;H]<S&TH]#&;H]<S&TH]#	U 		."5 	UH&;H]<S&TH]#&;H]<S&TH]#	U
 ?B7 	WI'<Y}=U'VIm$'<Y}=U'VIm$	WW rR   c                      t        j                         } | j                  d      }|t        d      z
  }|j                  }|S )Nr   )day)days)r"   nowreplacer#   month)current_datefirst_day_of_current_monthlast_day_of_last_month
last_months       r8   get_last_month_numberr    sH    <<>L ".!5!5!!5!< 8):KK (--JrR   c                  F    t        j                         } | j                  }|S r*  )r"   r  year)r  current_years     r8   get_current_yearr    s     <<>L  $$LrR   c                    g }| j                         D ]  \  }}|j                  d      s|dd }t        |d         }|dk(  rd}n'|dk(  rd}n|dk(  rd	}n|d
k(  rd}n|dk(  rd}n|dk(  rd}t        |      |k  rJd| }|| v r|j	                  d| |   i       n|j	                  dt               i       t        |      |k  rJ|||   |<   d| }|| v ||   d<    |D ]  }d|vrd|d<   d|d<    |S )Nchildr?   r   	Firstnamer   Lastnamer   Genderr   	Birthdate
birth_date
Allowanceschild_allowancesNotenotechildUserIdra  childDeleterL  r   r   )r  r"  r  ri  r  rQ   )		form_datachildrenr  valuefieldr4  user_id_key
delete_keyr  s	            r8   extract_children_datar    sX   Hoo' #@
U>>'""IEBLE#$*$#(" +%$,&*& h-5( +E73)+OOY	+0F$GH OOY$?@ h-5( &+HUOE" 'ug.J(2i(?HUOH%G#@H  *U*()E$%()E$%	* OrR   c                      t               | d<   | S )aU  
    Adds a unique user ID to the provided data dictionary.

    Process:
        - Generates a unique identifier for the user by calling `generate_uid()`.
        - Adds this generated ID to the `data_dict` under the key 'user_id'.
        - Returns the modified dictionary with the added 'user_id'.

    Args:
        - data_dict (dict): The dictionary containing user data that will be augmented with a new 'user_id'.

    Returns:
        - dict: The input dictionary with an additional key-value pair:
            - 'user_id' (str): The unique identifier generated for the user.

    Example Usage:
        - Use this function to add a unique 'user_id' to user data before saving it to a database or performing other operations.

    Notes:
        - This function assumes the existence of `generate_uid`, a function that generates unique IDs.
    ra  )rQ   )r8  s    r8   add_user_idr    s    0 (>Ii rR   c                 .   t               }g }| D ]  }|j                  d      dd}|d   |v rt        |       d|d<   nt               }t	        |||       d|d<   |j                  dd      d	k(  rt        |d          d|d<   |j                  |        |S )
u  
    Updates information for each child associated with a user, determining whether to insert, update, or delete
    child data based on the provided input.

    Process:
        - Retrieves all existing user IDs to check if each child ID already exists.
        - For each child entry in `childrens_data`:
            - If the child ID exists, updates the child’s data.
            - If the child ID does not exist, inserts new child data with a unique ID.
            - If a 'delete' flag is set to True, deletes the child data.
        - Records the operation performed for each child ('insert', 'update', or 'delete') and stores it in a list.

    Args:
        - childrens_data (list of dict): List of dictionaries containing data about each child, including 'user_id' and 'delete' flags.
        - user_id (str): The unique identifier of the user to whom the children are associated.

    Returns:
        - list of dicts: A list where each dictionary contains:
            - 'user_id': The ID of the child processed.
            - 'operation': The operation performed ('insert', 'update', 'delete') for that child.

    Example Usage:
        - When updating children information for a user, this function decides for each child whether to insert, update,
          or delete the data and returns a summary of the actions taken.

    Notes:
        - Assumes the existence of helper functions like `get_all_user_id`, `delete_child_data`, `update_child_data`,
          `generate_uid`, and `insert_child_data` for performing database operations.
    ra  Nra  rD  r   rD  rN  rL  FT)r   rY   r   rQ   r   r   r  )childrens_datara  user_idsresults
child_dataoperation_resultchild_ids          r8   update_childrensr    s    @  H G % )
'1~~i'@tT i H,j),4[) $~Hj'8<,4[) >>(E*d2j34,4[) 	'(-)2 NrR   c                    t               }|xs g D ch c]  }||	 }}| j                  dd      dd}d| v r| d   dk(  rt        | d          d|d<   |S | j                  dd      |v rd| v r| d   dk(  rt        |        d	|d<   |S | j                  dd      |vrt	               }t        | ||       d
|d<   |S c c}w )a  
    Updates partner information for a user, determining whether to insert, update, or delete
    partner data based on the provided input.

    Process:
        - Retrieves all existing user IDs to check if the specified partner ID already exists.
        - Checks the `partner_delete` flag in `partner_data` to determine if the partner should be deleted.
        - If the partner exists and is not marked for deletion, updates their information.
        - If the partner ID is new, generates a unique ID and inserts the new partner data.
        - Records the operation performed ('insert', 'update', or 'delete') for audit purposes.

    Args:
        - partner_data (dict): Dictionary containing data about the partner, including 'partner_id' and 'partner_delete'.
        - user_id (str): The unique identifier of the user to whom the partner is associated.

    Returns:
        - dict: A dictionary containing:
            - 'user_id': The partner ID or None if not provided.
            - 'operation': The operation performed ('insert', 'update', 'delete', or None if no operation was executed).

    Example Usage:
        - When partner information is updated, this function decides whether to insert, update, or delete the
          partner data and returns a summary of the action taken.

    Notes:
        - Assumes the existence of helper functions like `get_all_user_id`, `delete_partner_data`, `update_partner_data`,
          `generate_uid`, and `insert_partner_db` for database operations.
    N
partner_idr  partner_deleteyesrL  rD  nor   rN  )r   rY   r   r   rQ   r   )rH  ra  r  uclean_user_idsr  r  s          r8   update_partnerr  ]  s   >  H"*.bCAQ]aCNC $0#3#3L$#GVZ[<'L9I,Je,SL67(0%  
		,	-	?DTXdDdiu  wG  jH  LP  jPL)(0%  
		,	-^	C!^
,<(0%1 Ds
   B;B;c                     t        ddd      }|D cg c].  }t        |j                  d            t        |      k(  s*|d   0 }}|r|d   rd|  d|d    d	}|S d|  d
}|S c c}w )uP  
    Generates a message for notifying Jan about a new Planday account creation for a user, including 
    the relevant approval description if found.

    Process:
        - Retrieves a list of approval descriptions from the 'users_approval' foreign key table.
        - Searches the approval list to find a description matching `approval_id`.
        - Constructs a message for Jan containing the user’s name and relevant approval description.
        - If no matching approval description is found, constructs a message indicating that no approval was located.

    Args:
        - user_name (str): The name of the user for whom a Planday account was created.
        - approval_id (str or int): The ID of the approval related to the user's Planday account creation.

    Returns:
        - str: A formatted message for notifying Jan about the new Planday account and its approval status.

    Example Usage:
        - When a new Planday account is created, call this function to generate a message notifying Jan, including
          approval details if available.

    Notes:
        - This function assumes `get_data_from_fk_table` retrieves a list of approvals from a foreign key table
          with details including 'id' and 'description'.
        - The message includes a link to Jan's Telegram profile using his Telegram user ID (6391610367).
    r  r   Tr6   r  r   uF   Hallo <a href="tg://user?id=6391610367">Jan</a>, für den Mitarbeiter z0 wurde ein Planday-Account mit der Genehmigung "u   " erstellt. Bitte überprüfen!uk    wurde ein Planday-Account erstellt, aber es wurde keine passende Genehmigung gefunden. Bitte überprüfen!)r   rz  rY   )	user_nameapproval_idapproval_listr  r~  r   s         r8   inform_jan_bc_user_new_plandayr    s    : ++;QEM +8`Q3quuT{;KsS^O_;_- `H` HQK((1{2bcklmcnbo p&& K((1{ 3&&
 K as
   +A$A$c           	      $   | g}g }|D ]e  j                         D 	ci c]  \  }}	|j                         dk7  s||	 c}	}t        fd|D        d      }
|
d<   |j                         g g }|r3|sd}d|v r|j	                  dd       |d   |d<   |j                  |       g }t        |t              rt        j                  |      }|r2|D ]-  }|j                  |d   |d   |j                  dd      d       / ||||d	}|S c c}	}w )
a:  
    Converts the given user and children data into the required structure with 'user_data' and 'child_data' keys.

    Args:
    - user_data (dict): Dictionary representing the main user data.
    - children_data (list[dict]): List of dictionaries representing the children data.
    - children_operations (list[dict]): List of dictionaries representing operations for each child.

    Returns:
    - dict: A dictionary with 'user_data' (list of user details) and 'children_data' (list of children details), 
            including operation details for each child.
    rL  c              3   X   K   | ]!  }|d    j                  d       k(  s|d    # yw)ra  rD  N)rY   )r  opr  s     r8   r  z+convert_to_log_structure.<locals>.<genexpr>  s0     wBQST]Q^bgbkbklubvQv;ws   *
*NrD  r  r6   rn   )r6   rn   rD  )rG  rO  rH  rR  )
r  lowernextr  rS  rf  rz  r,  r-  rY   )rG  rO  children_operationsrH  partner_operation	file_listformatted_user_dataformatted_children_datakvchild_operationformatted_partner_dataformatted_file_listr}   
final_datar  s                  @r8   convert_to_log_structurer    sR     %+ ! 
."'++-I$!Q17793HAIw:Mwy}~ -k 	 &&u-
.    $ |+-t4$5k$B[! 	%%l3)S!$$Y/	 	zD&&T$Zf\`\d\deprv\w'xy	z
 )0.)	J Q Js
   DDc                     t        | t              r,| D ]%  }|j                         D ]  \  }}|dk(  sd ||<    ' | S t        | t              r#| j                         D ]  \  }}|dk(  sd | |<    | S )N )rf  listr  dict)r   rU  r  r  s       r8   replace_empty_with_noner  	  s    $ 	&E#kkm &
UB;!%E#J&	& K	 
D$	**, 	!JC{ S		! KrR   c                     d| d| d}| j                         j                  |dddd      j                         }|j                  dg       }|sy	|d
   d   S )aI  
    Find the Google Drive folder ID based on folder name within a specified parent folder.

    Args:
        service: Authenticated Google Drive service instance.
        folder_name (str): Name of the folder.
        parent_id (str): ID of the parent folder.

    Returns:
        str: Folder ID if found, None otherwise.
    z8mimeType='application/vnd.google-apps.folder' and name='z' and '' in parentsrh   zfiles(id, name)T)qspacesrV   includeItemsFromAllDrivesrW   rX   Nr   r6   )rX   r  rZ   rY   )r_   rx   ry   queryr   folderss         r8   find_folder_idr    s     G{mSZ[dZeeqrE}}##eGDUrv  JN#  O  W  W  YHll7B'G1:drR   c                 "   t               }	 t        j                  j                  |t              }	 t        dd|      }t        |||      }|sd
gS g }| D ]  }	|	j                  d      d   }
t        j                  |	      \  }}|sd}|
|gd}t        |	|      }	 |j                         j                  ||dd      j!                         }|j#                  |
|d   |d   dd        |S # t
        $ r#}t        j                  d|       dgcY d}~S d}~ww xY w# t
        $ r#}t        j                  d|       d	gcY d}~S d}~ww xY w# t
        $ r9}d|
 d| }t        j                  |       |j#                  |       Y d}~3d}~ww xY w)a  
    Uploads multiple files to Google Drive to a specified folder by folder name, within a specified parent folder.
    Automatically determines the MIME type of each file based on its extension.

    Args:
        file_paths (list of str): List of file paths.
        folder_name (str): The name of the Google Drive folder where the files will be uploaded.
        parent_id (str): ID of the parent folder where the target folder resides.

    Returns:
        list: A list of URLs or error messages for each file uploaded.
    rd   rf   rg   Nrh   ri   rj   rl   rm   z!Folder not found or inaccessible./r   zapplication/octet-stream)rn   rp   )mimetyperq   T)rr   
media_bodyrV   rW   rs   r6   rN  )rn   r   r6   rD  zError uploading the file r   )r   r   r   rt   ru   rv   r[   r\   r   r  r   	mimetypes
guess_typer   rX   rw   rZ   r  )
file_pathsrx   ry   rz   rk   r{   r_   r`   r  	file_path	file_name	mime_type_r|   mediar}   r\   error_messages                     r8   upload_files_to_google_driver  .  s    ,-I>%11KKf L &5;? wY?I344G   *	OOC(,	 ++I6	12I !{
  	I>	*==?))}Wh  }A)  B  J  J  LDNNIT-5HtTXzhpqr%*0 NW  >FJ<==>  5=qA3445>  	*7	{"UGLMMM-(NN=))	*sS   %C. D AE.	D7DDD	E	&E>E	E		F.F		Fc                    t               }	 t        j                  j                  |t              }	 t        dd|      }t        || |      }|t        j                  d
       g S 	 d| d}|j                         j                  |dddd      j                         }|j                  dg       }	|	D 
cg c]  }
d|
d   j                         vs|
 }}
|S # t
        $ r }t        j                  d|       Y d}~yd}~ww xY w# t
        $ r }t        j                  d|       Y d}~y	d}~ww xY wc c}
w # t
        $ r"}t        j                  d|       g cY d}~S d}~ww xY w)a  
    List all files in a specified Google Drive folder using the folder name.

    Args:
        service: Authenticated Google Drive service instance.
        folder_name (str): The name of the folder.
        parent_id (str): The ID of the parent folder (default is 'root' for the main directory).

    Returns:
        list: A list of dictionaries, each containing details about the files within the folder.
    rd   rf   Nrg   rh   ri   rj   rl   rm    Folder not found or inaccessible'r  zGfiles(id, name, mimeType, size, webViewLink, createdTime, modifiedTime)T)r  r  rV   rW   r  rX   DELETEDrn   zFailed to retrieve files: %s)r   r   r   rt   ru   rv   r[   r\   r   r  rX   r  rZ   rY   upper)rx   ry   rz   rk   r{   r_   r`   r  r  rX   r}   filtered_filess               r8   list_files_in_folderr  m  sr    ,-I<%11KKf L &3;?
 wY?I89	I;l+--/&&w  HQ  ei  EI&  J  R  R  TGR(+0Z4IT&\EWEWEY4Y$ZZ+  <FJ;<  3=qA23 [ 4a8	se   %C D 'AD5 5D0D0D5 	D!C<<D	D-D((D-0D5 5	E >EE E c                    t               }	 t        j                  j                  |t              }	 t        dd|      }t        || |      }|t        j                  d
       y	 |j                         j                  |dd      j                         }||j                  dg       v r|S t        j                  d       y# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w# t
        $ r }t        j                  d|       Y d}~y	d}~ww xY w# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w)a  
    Get a specific file in a specified Google Drive folder using the folder name and file ID.

    Args:
        folder_name (str): The name of the folder.
        file_id (str): The ID of the file to retrieve.
        parent_id (str): The ID of the parent folder (default is 'root' for the main directory).

    Returns:
        dict: A dictionary containing details about the file if found, or None if the file is not found.
    rd   rf   Nrg   rh   ri   rj   rl   rm   r  zIid, name, mimeType, size, webViewLink, createdTime, modifiedTime, parentsTrT   rp   z,File is not located in the specified folder.zFailed to retrieve file: %s)r   r   r   rt   ru   rv   r[   r\   r   r  rX   rY   rZ   info)	rx   file_idry   rz   rk   r{   r_   r`   r}   s	            r8   get_file_by_id_in_folderr    s4    ,-I<%11KKf L &3;? wY?I89}}""^" # 
 ')	 	 B//KLLGH=  <FJ;<  3=qA230  3Q7sM   %C C. &AD ,D 	C+C&&C+.	D7DD	E#D>>Ec                     d|  }t        t              }||d}	 t        j                  |i |ddi|      }|j                  dk(  ryy# t        j
                  $ r}t        d	|         d }~ww xY w)
Nr   )r   r   r   r   r   r   TFr   )r   r   r   r   r   r   r   )r   r   r   r   r   r   r   r{   s           r8   reset_planday_namer    s    ::,
GC'(9:O  G
<<KK8JK

 3&$$  $%s   -A
 
A1A,,A1c                    g }i }| D ]k  }|j                  d      s#|j                  d      s|j                  d      s8| |   }|s@t        |j                        sV|j                  d      r|t        d      d }d}n2|j                  d      r|t        d      d }d}n|t        d      d }d}t	        |j                        }|}d| }	d	| }
|j                  |	d      j                  d
d      }|j                  |
d      j                  d
d      }|r|}|r| d| }| | d| }d||<   t        j                  j                  d|      }|j                  |       |j                  |       n ||fS )a  
    Saves uploaded files to a temporary directory, modifies filenames with date information if available, and 
    returns paths and statuses of saved files.

    Process:
        - Iterates over each uploaded file, filtering to include only files with allowed prefixes.
        - For valid files:
            - Extracts an identifier and prefix from the file key to construct a modified file name.
            - Checks if corresponding start and end dates are provided in `form` and appends them to the filename.
            - Saves the file to a temporary directory with the modified name.
        - Tracks saved file paths and filenames with statuses in dictionaries.

    Args:
        - files (dict): A dictionary of uploaded files, typically from `request.files`.
        - form (dict): A dictionary of form data, typically from `request.form`.

    Returns:
        - tuple: A list of saved file paths and a dictionary with filenames and their statuses.

    Example Usage:
        - After uploading multiple files, this function saves each to `/tmp` and modifies filenames with any
          provided start and end dates.

    Notes:
        - The function assumes `allowed_file` is defined to validate file extensions.
        - Dates are expected in 'YYYY-MM-DD' format and are reformatted to 'YYYYMMDD' for filenames.
        - Files are saved in `/tmp`, and the saved paths are returned for further processing.
    file_front_
file_back_file_Nfront_back_r  
startDate_endDate_-_to_r  Createdz/tmp)r"  allowed_filefilenameri  r	   rY   r  ospathjoinsaver  )rX   formr  r  file_keyr}   rQ  r   r  start_date_keyend_date_key
start_dateend_date	date_partr  s                  r8   save_files_tmpr    s   < JI  /)##M2h6I6I,6W[c[n[nov[w X L/""=1%c-&8&9:
!$$\2%c,&7&89
 %c'lm4
 't}}5H  H  **6N%j\2L ."5==c2FJxxb199#rBH $	#-,d8* =I$Xj\9+> #,Ih VX6IIIi i(_/)d y  rR   c                 `    h d}d| v xr% | j                  dd      d   j                         |v S )a  
    Checks if a file has an allowed extension based on predefined allowed file types.

    Process:
        - Defines a set of allowed extensions for files.
        - Checks if the file has an extension by looking for a period ('.') in the filename.
        - Extracts the file extension by splitting the filename on the last period.
        - Verifies if the extension (converted to lowercase) is in the set of allowed extensions.

    Args:
        - filename (str): The name of the file to check.

    Returns:
        - bool: True if the file has an allowed extension, otherwise False.

    Example Usage:
        - For a file "document.pdf", the function will return True if 'pdf' is in `ALLOWED_EXTENSIONS`.
        - For a file "image.bmp", the function will return False if 'bmp' is not in `ALLOWED_EXTENSIONS`.

    Notes:
        - This function assumes that only the last period in the filename indicates the extension.
        - File extensions are case-insensitive.
    >   jpgpdfpngtxtjpeg.r   )rsplitr  )r  ALLOWED_EXTENSIONSs     r8   r  r  >  s:    4 > (?WxsA6q9??AEWWWrR   c           	      L   g }| D ],  }	 t        j                  |       |j                  |dd       . |S # t        $ r |j                  |dd       Y Pt        $ r |j                  |dd       Y nt
        $ r(}|j                  |dt        |      d       Y d}~d}~ww xY w)a  
    Attempts to delete each file in a list of file paths and records the result of each operation.

    Process:
        - Iterates through each file path in `file_paths`.
        - Tries to delete the file at each path and records the status of each deletion attempt.
        - Handles different exceptions to capture specific errors:
            - `FileNotFoundError`: File does not exist at the specified path.
            - `PermissionError`: Insufficient permissions to delete the file.
            - Other exceptions are recorded with the specific error message.

    Args:
        - file_paths (list of str): List of file paths to attempt deletion.

    Returns:
        - list of dicts: Each dictionary contains:
            - 'file_path' (str): The path of the file attempted for deletion.
            - 'status' (str): Indicates the result ('deleted', 'not found', 'permission denied', or 'error').
            - 'error' (str, optional): Error message if an unspecified exception occurred.

    Example Usage:
        - Given a list of file paths, the function will attempt to delete each one, returning a list of status results.

    Notes:
        - This function provides detailed feedback for each file deletion attempt, useful for auditing and debugging.
    rJ  )r  r^   z	not foundzpermission deniedr\   )r  r^   r\   N)r  remover  FileNotFoundErrorPermissionErrorrv   rz  )r  r  r  r{   s       r8   remove_filesr*  ^  s    8 G   Y		YIIi NNiHI	Y( N ! 	LNNkJK 	TNN>QRS 	YNNgPSTUPVWXX	Ys!   )6B#B#3B#;BB#c                 `    t        j                  d      }|j                  |       }|r|d   S dS )as  
    Extracts a date from a file name based on an 8-digit pattern (YYYYMMDD format).

    Process:
        - Compiles a regular expression to match exactly 8 consecutive digits in the filename.
        - Searches the filename for occurrences of 8-digit sequences.
        - If any dates are found, returns the last one in the list.
        - If no dates are found, returns None.

    Args:
        - filename (str): The name of the file from which to extract a date.

    Returns:
        - str or None: The extracted date string in 'YYYYMMDD' format if found, otherwise None.

    Example Usage:
        - For a filename "report_20221130_final.pdf", the function will return "20221130".
        - If no 8-digit sequence is present, the function returns None.

    Notes:
        - This function assumes the date format is always 'YYYYMMDD' and looks for an exact 8-digit match.
    z\d{8}r   N)recompilefindall)r  date_patterndatess      r8   extract_date_from_filenamer1    s7    2 ::h'L   *E 59'4'rR   c                 0   t        j                  d      }i }|xs g D ]  }|j                  d      xs dj                         }|j	                  |      }|s:|j                         \  }	}
	 t        j                  |
d      j                         }|j                  |	      }|||kD  s|||	<    d|v rTd|v rO|d   |d   k(  rCt        j                  t        d            j                         }|d   |kD  rt        | ||       yyyyy# t        $ r Y w xY w)	z
    url_name: list of dicts like
      {'name': 'front_approval_20300620', 'url': '...', 'id': '...', 'operation': 'insert'}
    z^(front|back)_approval_(\d{8})$rn   r  r-   NfrontbackzEurope/Zurich)r,  r-  rY   stripmatchgroupsr"   strptimer   r/  r  r$   r  )r   url_namer   r   patfoundr   rn   msideyyyymmddr  prevtoday_zurichs                 r8   /check_if_docs_are_valid_and_update_planday_namerA    s#    **7
8CEB  &B--/IIdOh	!!(H5::<A yy<1t8E$K %FeOg%-0O||H_$=>CCE>L(z:yA ) 1PO  		s   ,$D			DDc                 Z   g }| D ]  }|d   j                         j                  dd      }g }|d   dk(  r|j                  d|        n0|d   dk(  r(|j                  d|        |j                  d	|        |D ci c]  }|d
 }}|D ci c]  }|d }}d
}	|D ]<  }
|D ]5  }||
d   v sd||<   d|
d   v sd|
d   v rd}	t        |
d         }|s1|||<   7 > t	        |j                               }d}|r|	s|d   dk(  rd}n{|d   dk(  r(d}|j                         D ]  }|st        |kD  sd} nM nK|d   dk(  rCd}|j                         D ].  }|s|j                  d      \  }}|t        cxk  r|k  r*n d} n |j                  |d   |	s|ndd        |S c c}w c c}w )a  
    Checks if required documents are present and meet expiration or validity criteria based on the given specifications.

    Process:
        - For each document specification in `document_specs`:
            - Normalizes the document name to create the expected file names based on the `sites` value.
            - Checks if required files exist in `file_metadata` and if they are expired or deleted.
            - Extracts dates from file names to validate document dates or ranges.
        - Determines document status:
            - Status 1 if all required files are present and meet date requirements.
            - Status 0 if any required file is missing, expired, or deleted.

    Args:
        - document_specs (list of dict): Specifications for each required document, containing 'name', 'sites', and 'needed_date' keys.
        - file_metadata (list of dict): Metadata for each available file, containing 'name' and optionally dates or status.

    Returns:
        - list of dicts: Each dictionary contains the document name and its status:
            - 'status' 1 if all requirements are met (file presence, date validity).
            - 'status' 0 if any requirement fails.

    Example Usage:
        - `document_specs` specifies documents to check, including number of required sides and date needs.
        - `file_metadata` contains files, and the function verifies if all required documents are present and valid.

    Notes:
        - The function uses helper functions like `extract_date_from_filename` to extract dates from file names.
        - Assumes a `today` variable is defined externally, representing the current date for date comparisons.
    rn   r   r  sitesr   r  r<   r  r	  FNTEXPIREDr  r   needed_dater  )rn   r^   )r5  r  r  r1  allvaluesr   r   )document_specsr|   r  doc	base_namerequired_filesrn   file_exists
file_datesfile_expiredr}   req_fileextracted_dateall_files_presentdocument_statusr   r  r  s                     r8   check_document_requirementsrS    s4   > G  ?dK%%'//S9	 w<1!!K	{";<\Q!!F9+"67!!E)"56 0>>ttU{>>-;<TdDj<
< " 	>D* 
>tF|+,0K) DL0If4M'+ &@V%MN%/=
8,
>	>   2 2 45  \=!Q&"#]#q("#&--/ D*+
 ]#q("#&--/ "D/3zz&/A,
H *e ?x ?./O!" 	Fl`abc?dB Ni ?<s   ?
F#
F(c                     t        j                  d      }|j                  |       }|rN|j                  d      r&|j                  d      }|j                  d      }||fS d }|j                  d      }||fS y)Nz(\d{8})(_to_(\d{8}))?r<   r   re  NN)r,  r-  searchgroup)r  r/  r6  r  r  s        r8   extract_datesrX  8  sx    ::67L)E;;q>QJ{{1~H 8## J{{1~H8##rR   c           
          t               }|D ci c]  }|d   |d    }}| D ]E  }|d   }|j                         D ]+  \  }}||v st        |      \  }	}
t        |||	|
|d           E G yc c}w )a<  
    Inserts document details for a user into the database based on file information.

    Process:
        - Retrieves document foreign key mappings from the database to match file names with document types.
        - Loops through each file in `url_name` and matches the file name with a document type from `doc_dict`.
        - Extracts start and end dates from the file name, if available, and inserts these details into the database.
        - Calls `insert_docs_db` to perform the actual database insertion for each matched document.

    Args:
        - url_name (list of dicts): List of dictionaries, each containing 'name' (file name) and 'url' (file URL) keys.
        - user_id (str): Unique identifier for the user to whom the documents belong.

    Returns:
        - None

    Example Usage:
        - If `url_name` contains files with names matching known document types, those documents are processed and inserted
          into the database with associated metadata (e.g., start and end dates).

    Notes:
        - This function assumes access to `get_doc_fk_table` to fetch document types and `insert_docs_db` for database insertion.
        - It also assumes `extract_dates` is a helper function to extract date ranges from the file name.
        - Error handling is not included and could be added for robustness.
    rn   r6   r   N)r    r  rX  r!   )r9  ra  docsrI  doc_dictrX   r  doc_namedoc_idr  r  s              r8   insert_users_docr^  F  s    8 D2673FSY&7H7  = !) 0 	Hf8#'4X'>$
H w
HeElS		 8s   A,c                    t               }	 t        j                  j                  |t              }	 t        dd|      }d	|i}	 |j                         j                  | |d
      j                         }|S # t
        $ r }t        j                  d|       Y d }~yd }~ww xY w# t
        $ r }t        j                  d|       Y d }~yd }~ww xY w# t
        $ r }t        j                  d|       Y d }~y d }~ww xY w)Nrd   rf   rg   rh   ri   rj   rl   rm   rn   T)rU   rr   rW   zFailed to rename file: %s)r   r   r   rt   ru   rv   r[   r\   r   rX   r   rZ   )r   rn   rz   rk   r{   r_   r|   updated_files           r8   rename_filera  t  s    +-I<%11KKf L &3;?
 TNM}}--W=dh-iqqs  <FJ;<  3=qA23  115sF   %A7 B# 1C 7	B  BB #	C,CC	C8C33C8c                     | j                  dd      }t        j                  |      }i }|D ]  }d|d   v sd|d   v rd||d   <    |S )Nr  "r  rn   rD  r  )r  r   r   )
files_jsoncorrected_jsonrX   
files_dictr}   s        r8   convert_filesrg    sn    ''S1N JJ~&E J  -V$	T&\(A $-
4< - rR   c                     t        | t              rt        j                  |       } t	        | d   d   d         }g }|D ]  }|j                  |d   |d   d        || d<   t        |       S )a  
    Adds current Google Drive file information to the provided user data.

    Process:
        - Checks if the `data` parameter is a string and converts it to a dictionary if necessary.
        - Retrieves a list of files associated with the user's Google Drive folder.
        - Converts each file entry to a simplified dictionary with only the file ID and name.
        - Updates the `data` dictionary to include the current file list under the 'files_data' key.

    Args:
        - data (str or dict): The user data, which may include previous records of files or other information.
          If `data` is a string, it will be converted to a dictionary.

    Returns:
        - str: The updated user data as a string, now including a 'files_data' key containing the current file list.

    Example Usage:
        - If `data` is a string representation of a dictionary, the function first converts it, retrieves the list of
          current files, and adds them to `data`. The function then returns the modified data as a string.

    Notes:
        - This function assumes `list_files_in_folder` is a helper function that fetches files from a Google Drive folder
          using the user's unique `user_id`.
    rG  r   ra  r6   rn   )r6   rn   rR  )rf  rz  r,  r-  r  r  )r   
files_listfinal_file_listr}   s       r8   add_current_filesrk    s    6 $% &d;&7&:9&EFJ O  Id4j$v,GHI )D t9rR   c                     t        | t              rt        j                  |       } | j	                  dg       D ]  }|d   |v sd|d<    t        |       S )NrR  r6   rL  rD  )rf  rz  r,  r-  rY   )r   ids_to_remover}   s      r8   remove_files_by_file_idrn    sZ    $% r* ):& (D) t9rR   c                 P    t        | t              rt        j                  |       } | S r*  ry  r   s    r8   str_to_listrp    s!    $%KrR   c                     g }| D ])  }	 t        j                  |      }|j                  |       + |S # t        t        f$ r
}Y d}~Bd}~ww xY w)a0  
    Converts a list containing string representations of dictionaries into a list of actual dictionaries.
    
    Args:
    - data_list (list): List containing string representations of dictionaries.
    
    Returns:
    - list[dict]: A list of dictionaries parsed from the input list of strings.
    N)r,  r-  r  r/  r.  )rT  converted_listr   parsed_itemr{   s        r8   parse_file_datart    s]     N 	**40K!!+.  K( 		s   &3AAc                     d|  }t        t              }ddgi}t        j                  |||      }|j                  dk(  s|j                  dk(  ryy)	Nr   r   i\) r   r   r   TF)r   r   r   r   r   )r   r   r   r   r   s        r8   put_driver_in_11KA_in_plandayrv    s\    ::,
GC'(9:O6(G ||CwGHs"x';';s'BrR   c                    |r/|r-|j                  dd      }|j                  dd      }d| d| }n6|r|j                  dd      }d| }n|r|j                  dd      }d| }nd}g }| D ]  }t        j                  j                  |      \  }}	t        j                  j	                  |	      \  }
}|
 | | }t        j                  j                  ||      }t        j                  ||       |j                  |        |S )a  
    Appends a date suffix (validFrom and/or validUntil) to each file name in the given file paths.

    Args:
        file_paths (list): List of file paths to rename.
        valid_from (str): Start date in the format 'YYYY-MM-DD'. Defaults to None.
        valid_until (str): End date in the format 'YYYY-MM-DD'. Defaults to None.

    Returns:
        list: List of updated file paths with the date suffix.
    r  r  r  )r  r  r  r   splitextr  renamer  )r  
valid_fromvalid_untilformatted_fromformatted_untilfile_name_suffixfile_paths_with_datesr  	directoryoriginal_file_namern   extnew_file_namenew_paths                 r8   append_date_suffix_to_filesr  	  s$    k#++C4%--c26~.a/@A	%--c26/0	#++C4~./   /(*d(;%	%GG$$%78	c&!1 23%877<<	=9
		$!$$X./ ! rR   c                 X    t        j                  d|       }|r|j                  d      S dS )z
    Extracts the file ID from a Google Drive file URL.

    Args:
        url (str): The Google Drive file URL.

    Returns:
        str: The extracted file ID, or None if the URL does not contain a valid file ID.
    z/d/([a-zA-Z0-9_-]+)r   N)r,  rV  rW  )r   r6  s     r8   extract_google_drive_file_idr  0	  s*     II,c2E"5;;q>,,rR   c                 @    g }| D ]  }|j                  ||           |S r*  )r  )dict_in_lostr  new_listr   s       r8   create_list_with_specificr  =	  s+    H #S	"#OrR   old_datanew_datareturnc                      t        |      dk(  r,t        |d   t        t        t        f      rt        |d         }|st        d      t         fd|D              S )z
    Return True if any of the explicitly provided keys differ between old_data and new_data.
    Usage:
        address_changed(old, new, "address", "postal_code", "location", "country")
        # or
        address_changed(old, new, *my_keys_tuple)
    r   r   zSPass the keys to compare, e.g. address_changed(old, new, 'address', 'postal_code').c              3      K   | ]9  }t        j                  |            t        j                  |            k7   ; y wr*  )
_normalizerY   )r  r  r  r  s     r8   r  z value_changed.<locals>.<genexpr>R	  s0     Xaz(,,q/*ja.IIXs   ?A)ri  rf  r  tupler5  r/  r  )r  r  r6  s   `` r8   value_changedr  C	  sR     4yA~*T!WtUC.@AT!W~nooXSWXXXrR   g      4@)r   r   r   r   r   r   phone_prefixphone_numberr   
brith_dateextratimeoutr   r   r   r   r   r   r   r  r  r   r  r  r  c                $   | syt        t              }t        |t              syi }t	        d ||||fD              rg }|r|j                  |j                                dj                  d |xs d|xs dfD              j                         }|r|j                  |       |r|j                  |j                                dj                  d |D              }|r||d	<   |||d<   |||d<   |
|
|d<   ||#|xs d |xs d j                         }|r||d<   |	|	|d<   |r |j                         D ]  \  }}|	|||<    |syt        |       d|  }	 t        j                  |i |ddi||      }|j                  dk(  ryd
}	 |j                         }|j                  d      xs |j                  d      xs t!        |      }d|fS # t        $ r}dd
d| fcY d
}~S d
}~ww xY w# t        $ r |j"                  xs d}Y d|fS w xY w)aA  
    Update a Planday employee in one call. Only provided args are sent.

    Returns:
        (ok, status_code, error_message)
        - ok=True if API returned 204 No Content
        - status_code is the HTTP status (or None if request failed before HTTP)
        - error_message is a concise description if not ok
    )FNzMissing planday_id)FNzAuth headers not availablec              3   $   K   | ]  }|d u 
 y wr*  r  )r  r  s     r8   r  z*update_planday_employee.<locals>.<genexpr>	  s     
LQ1D=
Ls   r   c              3   &   K   | ]	  }|s|  y wr*  r  r  ps     r8   r  z*update_planday_employee.<locals>.<genexpr>	  s     La!L   r  r   c              3   &   K   | ]	  }|s|  y wr*  r  r  s     r8   r  z*update_planday_employee.<locals>.<genexpr>	  s     2!A2r  r   Nr   r   r   r   r   )FNz#No fields to update (empty payload)r   r   r   )r   r   r  FzRequest error: r   )TNerror_descriptionmessagezUnknown error)r   r   rf  r  r  r  r5  r  r  r   r   r   rv   r   r   rY   rz  r   )r   r   r   r   r   r   r   r  r  r   r  r  r  r   r   partstownr   numberr  r  r   r]   r{   err_textr   s                             r8   update_planday_employeer  T	  sl   > 2 ((9:Oot,:!#G 
L7K7"K
LLELL1xxLK$52x~2#FLLRRTLLELL1))2u22!(GI )'
 ) 	 l&> &B'(:';<BBD#)GK    KKM 	DAq}
	 C	'N::,
GC4||KK8JK	
 3 H0yy{88/0TDHHY4GT3t9 8  4tqc2334  099/80s1   G AG0 	G-G("G-(G-0HHempty_to_nonec                     t        | t              rDt        j                  dd| t        j                        }|r|dk(  s|j                         dk(  ry |S | S )Nz	^\s+|\s+$r  )flagsnone)rf  rz  r,  subUNICODEr  )r  r  ss      r8   r  r  	  sG    !SFF<Qbjj9a2gf)<HrR   )!1idjXBYaU6B5vcB5GiR3bKWScLvNowdfI)u
   GekündigtTNrU  )T)__doc__rD   rB   r[   r  r   google.oauth2r   googleapiclient.discoveryr   googleapiclient.httpr   googleapiclient.errorsr   google.oauth2.credentialsr   google_auth_oauthlib.flowr   werkzeug.utilsr	   typingr
   r   r   r  r  r   phonenumbers.phonenumberutilr   setupr   r   r   r   r   r   modelsr   r   r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r   r   r,  r  r,  zoneinfor$   basicConfigINFOr1   r&   TG_VERr'   ImportErrortelegram.extr(   r)   r*   r+   r,   r  r   r   r9   rQ   SERVICE_ACCOUNT_FILEru   rb   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rA  rZ  r]  rw  rg  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r*  r1  rA  rS  rX  r^  ra  rg  rk  rn  rp  rt  rv  r  r  r  r  boolr  rz  objectfloatr  r  r  r  rR   r8   <module>r     sb      	  ) + 0 , 1 6 * ( (   ! E w w ^  ^  ^  ^ (   
  	    ',, ' *')  [ [ 	)72 1  2	2	.,`"H0:-^#J")H$NDL%P(%P0:*X>FR\~Zz2DLB-^>?B9v.b>@ *=~(T2h8T!nX@3l(BB:cL+\0,,^
*	(!V-YD YD YD Y* "!%"! $#"&"& $)-)nn c]	n
 #n smn c]n n }n 3-n 3-n C=n  !n$ Df%&%n( )n* 
tXc]HSM1	2+n` iL  '&'s   H3 3H>=H>