Определение взаимозависимых лиц согласно ст. 105.1 Налогового кодекса РФ достаточно тривиальная задача для корпоративного юриста.

Обычно это не вызывает сложностей и, кажется, зачем здесь что-то автоматизировать?
Все верно, если общество два или три сравнить их составы СД нет никаких проблем. Но, если обществ больше двух десятков и при этом в разных обществах разные составы директоров в том числе по количеству членов? Здесь уже надо потратить время. Потратим его на программу, которая за нас вычислит взаимосвязанные стороны, анализируя составы советов директоров.

Немного определений


Напомним, что взаимозависимые лица определяются по ряду критериев, приведенных в НК РФ.

В пункте 2 ст. 105.1 НК РФ перечислены основания, согласно которым лица признаются взаимозависимыми. Итак, для целей налогообложения взаимозависимыми лицами являются:

  • организации, в случае если одна организация прямо и (или) косвенно участвует в другой организации и доля такого участия составляет более 25%;
  • физическое лицо и организация в случае, если такое физическое лицо прямо и (или) косвенно участвует в такой организации и доля такого участия составляет более 25%;
  • организации, в случае если одно и то же лицо прямо и (или) косвенно участвует в этих организациях и доля такого участия в каждой организации составляет более 25%;
  • организация и лицо (в том числе физическое лицо совместно с его супругом (супругой), родителями (в том числе усыновителями), детьми (в том числе усыновленными), полнородными и неполнородными братьями и сестрами, опекунами (попечителями) и подопечными), имеющие полномочия по назначению (избранию) единоличного исполнительного органа этой организации или по назначению (избранию) не менее 50% состава коллегиального исполнительного органа или совета директоров (наблюдательного совета) этой организации;
  • организации, единоличные исполнительные органы которых либо не менее 50% состава коллегиального исполнительного органа или совета директоров (наблюдательного совета) которых назначены или избраны по решению одного и того же лица (физического лица совместно с его супругом (супругой), родителями (в том числе усыновителями), детьми (в том числе усыновленными), полнородными и неполнородными братьями и сестрами, опекунами (попечителями) и подопечными);
  • организации, в которых более 50% состава коллегиального исполнительного органа или совета директоров (наблюдательного совета) составляют одни и те же физические лица совместно с их супругом (супругой), родителями (в том числе усыновителями), детьми (в том числе усыновленными), полнородными и неполнородными братьями и сестрами, опекунами (попечителями) и подопечными;
  • организация и лицо, осуществляющее полномочия ее единоличного исполнительного органа;
  • организации, в которых полномочия единоличного исполнительного органа осуществляет одно и то же лицо;
  • организации и (или) физические лица в случае, если доля прямого участия каждого предыдущего лица в каждой последующей организации составляет более 50%;
  • физические лица в случае, если одно физическое лицо подчиняется другому физическому лицу по должностному положению;
  • физическое лицо, его супруг (супруга), родители (в том числе усыновители), дети (в том числе усыновленные), полнородные и неполнородные братья и сестры, опекун (попечитель) и подопечный.

Ближе к делу


Оснований много, но нам интересно одно из них, а именно:
— организации, в которых более 50% совета директоров составляют одни и те же физические лица.

Данное основание отличается от остальных тем, что юристу необходимо сравнить все составы советов директоров в обществах на совпадение их членов более 50%.

Допустим все составы СД у нас сведены в таблицу Excel с советами директоров и общим количеством обществ более двадцати:



Нам необходимо, чтобы наша программа, пройдя по таблице, отбирала общества если там будет совпадение более чем 50 % от общего числа членов совета директоров. Далее программа должна выводить результат либо на экран, либо в файл.

Логически программа будет выполнять следующий алгоритм. Получив от пользователя ФИО директоров, она будет сравнивать их с каждой строкой Excel, где содержатся ФИО директоров по каждому обществу. То есть каждые три члена СД пользователя из 5-ти сравниваются с каждыми 3-мя из 5-ти в таблице. На данном этапе программа не будет анализировать СД с числом членов более 5, пропуская их.

Первые строки нашей программы будут стандартными:

import openpyxl
wb = openpyxl.load_workbook('sd3.xlsx')
sheet=wb.get_active_sheet()

Здесь мы импортировали модуль для работы с Excel и открыли файл с нашей таблицей.

Теперь мы предложим пользователю ввести пять членов СД, которых программа далее будет искать в обществах для сравнения. Всех введенных пользователем членов СД запишем в переменные a,b,c,e,f:

a=str(input("Директор-1: "))
b=str(input("Директор-2: "))
c=str(input("Директор-3: "))
e=str(input("Директор-4: "))
f=str(input("Директор-5: "))

Здесь важный момент. ФИО директора надо вводить без пробелов, например ИвановИ.И. В таком же виде ФИО директоров должны присутствовать в таблице Excel.

Создадим два списка. Один пустой, второй с обществами, где СД более чем с 5 членами:

found = []
found2=[1,10,11,12,13,14,18,27,31,32] # общества в таблице sd3,где СД  больше 5 чел.
h = open('55555.txt','a')

Также мы открыли текстовый файл 55555.txt, в который будем сохранять результаты.Совпадение СД более, чем на 50 % означает, что 3 члена из 5-ти должны совпасть. Значит программа должна произвести 10 проверок на совпадение фамилий. Именно столько комбинаций может быть для СД из 5-ти членов.

Поэтому, чтобы не писать 10 раз один и тот же код, делая программу громоздкой, мы создадим функцию и далее вызовем (исполним ) ее 10 раз.

Заряжаем функцию


Вот наша функция:

def myfun(x,y,z):   
   for rowOfCellObjects in sheet['B2':'L36']:      
      for cellObj in rowOfCellObjects:
         if cellObj.value ==None:
            continue
         #print (cellObj)
         if cellObj.value == x:
             for cellObj in rowOfCellObjects:
                 if cellObj.value == y:
                     for cellObj in rowOfCellObjects:
                         if cellObj.value == z:
                             d = list(cellObj.coordinate)
                             d[0]='A'
                             dd=d[0]+d[1]                   
                             if len(d)>2:
                                dd=d[0]+d[1]+d[2]
                             i=sheet[str(dd)].value   #значение из столбца А,где найдены совпадения 3-х директоров
                             q = cellObj.row
                             if i not in found and i not in found2:
                              found.append(i)
                              h.write (str(i)+'\n')

Разберем код.

Мы задали границы таблицы, по которой пойдет программа B2:L36. Если ячейка пустая, она будет пропущена.

Далее в функция будет рассматривать ФИО 1-го директора, если ФИО в ячейке соответствует ФИО, введенного пользователем, то функция идет дальше и сравнивает 2-го, затем 3-го директора. В конце функция формирует список обществ и записывает каждое из них в файл 55555.txt.
После того, как функция написана она автоматически не будет исполнена программой.

Ее необходимо вызвать:

myfun(a,b,c)

Но здесь мы вызвали функцию только для первой тройки директоров. Надо повторить вызов функции и для остальных комбинаций:

myfun(a,b,e)
myfun(a,b,f)
myfun(a,e,f)
myfun(a,c,e)
myfun(a,c,f)
myfun(b,c,e)
myfun(b,c,f)
myfun(b,e,f)
myfun(c,e,f)
h.close()

Запускаем


Запустим программу и введем в окне интерпретатора python любые 5-ть ФИО директоров из
тех, что хотя бы раз встречаются в таблице excel:



После того, как программа исполнится, заглянем в файл, который создаст программа —
55555.txt:



В файле – номера обществ, где были найдены совпадения. Надо обратить внимание, что это номера обществ из столбца А, а не номера строки Excel:



Программа готова, но необходимо помнить, что она не анализирует составы СД с членами более 5 человек!

Скачать код программы – здесь.

Скачать тестовую таблицу – здесь.

P.S.: любые совпадения в таблице с реальными личностями случайны.

Комментарии (10)


  1. BasicWolf
    06.08.2019 10:08

    Я правильно понимаю что эта задача сводится к


    Дано n множеств N1, N2,… Nn: ?1 ? k ? n |Nk| ? 1
    Найти множества: |Nk ? Nj| ? max(|Nk|, |Nj|) / 2: j ? k


    ?


    Потому что в этом случае решение укладывается в 3 строки:


    import itertools
    
    # данные, каждая цифра соотвествует директору
    data = (
        {1, 2, 3, 4}, 
        {1, 2, 3, 5}, 
        {3, 4, 5, 6, 7}
    )
    
    # решение
    for s0, s1 in itertools.combinations(data, 2):
        if len(s0 & s1) > max(len(s0), len(s1)) / 2:
            print(s0 & s1)


    1. zoldaten Автор
      06.08.2019 12:43

      Общий смысл задачи: проверить, что любые 3 директора из 5 предложенных пользователем должны находиться в строке с директорами общества.


      1. BasicWolf
        06.08.2019 16:34

        Спасибо! Но ведь тогда всё ещё проще:


        # предложенные директора
        managers_of_interest = {1, 2, 3} 
        
        data = (
            {1, 2, 3, 4}, 
            {1, 2, 3, 5}, 
            {3, 4, 5, 6, 7}
        )
        
        for s in data:
            if managers_of_interest & s:
                print(s)

        A в предыдущем примере так и вообще ничего вводить не надо — программа сама найдёт все организации в которых пересечение директоров больше 50%. И уже в этом результате имеет смысл искать конкретные имена.


        1. zoldaten Автор
          06.08.2019 17:37

          По 1-му коду:
          да, он выявил, что есть пересечение по 1,2,3, но в каких обществах не указал.
          Кроме того, пользователь вводит 5-ть человек, а не троих. Из этих 5-х надо понять кто трое есть в строке с обществом в любой комбинации.
          По 2-му коду.
          Вывод:
          {1, 2, 3, 4}
          {1, 2, 3, 5}
          {3, 4, 5, 6, 7}
          Но в третьей строке нет пересечения! Там нет троицы «1,2,3».


          1. BasicWolf
            06.08.2019 18:03

            Поторопился:


            ...
            if managers_of_interest.issubset(s)

            По 1-му коду — ну да, это лишь "заготовка" демонстрирующая основную идею. К ней можно приделать и красивый вывод и вообще всё что угодно :)


            1. zoldaten Автор
              06.08.2019 21:57

              В таком случае рискну предположить, что сам python всего лишь заготовка, демонстрирующая основную идею )


  1. Satim
    06.08.2019 10:27

    как то так

    def myfun(x, y, z):
        # получим множество кортежей с директорами
        directors = set(tuple(x.value for x in sheet['C2':'L36'].rows))
        # Получим список всех идентификаторов организаций
        companies = tuple(x.value for x in sheet['А2':'A36'])
    
        data_dict = dict(zip(companies, directors))
    
        checked_directors = set(x, y, z)
    
        for key, value in data_dict:
            if checked_directors.issubset(value):
                h.write(str(key) + '\n')


    1. zoldaten Автор
      06.08.2019 12:38

      Пока ошибка:
      directors = set(tuple(x.value for x in sheet['B2':'L36'].rows))
      AttributeError: 'tuple' object has no attribute 'rows'


      1. Satim
        06.08.2019 17:15

        Библиотеку было лень ставить так что в слепую код писал. Главное смысл вы поняли надеюсь)


        1. zoldaten Автор
          06.08.2019 17:40

          Уверен, что задача имеет более изящное решение. Но ваш код не удалось прокачать, хотя все библиотеки стоят. Если вы доведете до ума, буду очень благодарен.