]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Merge branch 'develop' into feature_smirnov_erp-105_final_fact_motivation
authorAlexander Smirnov <fredeom@mail.ru>
Fri, 16 Aug 2024 13:26:26 +0000 (16:26 +0300)
committerAlexander Smirnov <fredeom@mail.ru>
Fri, 16 Aug 2024 13:26:26 +0000 (16:26 +0300)
# Conflicts:
# erp24/services/MotivationService.php

1  2 
erp24/actions/motivation/IndexAction.php
erp24/services/MotivationService.php

index 42fe9eadf75541264df82507c6b031c3b501d260,d2eecf565e8a07445e52ccf3b9cd45ce5c19291f..016d6df458b2501d9848e026e2d0efd56c7344df
@@@ -520,210 -551,460 +551,667 @@@ class MotivationServic
          }
      }
  
+     public static function calculateSales($store_id, $year, $month) {
+         $monthStart = date("Y-m-d 00:00:00", strtotime($year . '-' . $month . '-1'));
+         $monthEnd = date("Y-m-t 23:59:59", strtotime($year . '-' . $month . '-1'));
+         foreach (range(1, 5) as $ind) {
+             $weekStart = date("Y-m-d 00:00:00", strtotime("+" . (($ind - 1) * 7) . ' days', strtotime($monthStart)));
+             $weekEnd = date("Y-m-d 23:59:59", strtotime("+" . ($ind * 7 - 1) . ' days', strtotime($monthStart)));
+             if ($weekEnd > $monthEnd) {
+                 $weekEnd = $monthEnd;
+             }
+             $sales = Sales::find()
+                 ->where(['between', 'date', $weekStart, $weekEnd])
+                 ->andWhere(['store_id' => $store_id])
+                 ->andWhere(['operation' => Sales::OPERATION_SALE])
+                 ->asArray()->all();
+             $salesIds = ArrayHelper::getColumn($sales, 'id');
+             // Ищем чеки-возврат на текущие чеки
+             $returnSales = Sales::find()->where(['operation' => Sales::OPERATION_RETURN, 'sales_check' => $salesIds])->all();
+             $returnSalesIds = ArrayHelper::getColumn($returnSales, 'sales_check');
+             // Offline sales
+             $salesOffline = Sales::find()->select(['SUM(summ) as total'])
+                 ->where(['between', 'date', $weekStart, $weekEnd])
+                 ->andWhere(['order_id' => ['', '0']])
+                 ->andWhere(['store_id' => $store_id])
+                 ->andWhere(['operation' => Sales::OPERATION_SALE])
+                 ->andWhere(['NOT IN', 'id', $returnSalesIds])
+                 ->asArray()->one();
+             // Online sales
+             $salesOnline = Sales::find()->select(['SUM(summ) as total'])
+                 ->where(['between', 'date', $weekStart, $weekEnd])
+                 ->andWhere(['NOT IN', 'order_id', ['', '0']])
+                 ->andWhere(['store_id' => $store_id])
+                 ->andWhere(['operation' => Sales::OPERATION_SALE])
+                 ->andWhere(['NOT IN', 'id', $returnSalesIds])
+                 ->asArray()->one();
+             $motivation = Motivation::find()->where(['store_id' => $store_id, 'year' => $year, 'month' => $month])->one();
+             $motivationValueGroup = MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one();
+             foreach (['Оффлайн продажи', 'Онлайн продажи'] as $topicInd => $topic) {
+                 $motivationCostsItem = MotivationCostsItem::find()->where(['name' => $topic])->one();
+                 /** @var $motivationCostsItem MotivationCostsItem */
+                 if ($motivation) {
+                     $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivation->id,
+                         'motivation_group_id' => $motivationValueGroup->id, 'value_id' => $motivationCostsItem->code])->one();
+                     if (!$motivationValue) {
+                         $motivationValue = new MotivationValue;
+                         $motivationValue->motivation_id = $motivation->id;
+                         $motivationValue->motivation_group_id = $motivationValueGroup->id;
+                         $motivationValue->value_id = $motivationCostsItem->code;
+                         $motivationValue->value_type = $motivationCostsItem->data_type;
+                     }
+                     $motivationValue->value_float = [$salesOffline, $salesOnline][$topicInd]['total'];
+                     $motivationValue->save();
+                     if ($motivationValue->getErrors()) {
+                         throw new \Exception(Json::encode($motivationValue->getErrors()));
+                     }
+                 }
+             }
+         }
+     }
+     public static function calculateMonthForecast($store_id, $year, $month) {
+         $motivationCostsItem = MotivationCostsItem::find()->all();
+         $motivationCostsItemCodes = ArrayHelper::getColumn($motivationCostsItem, 'code');
+         $additionalItemsKeys = array_keys(self::$additionalItems);
+         $items = array_merge($motivationCostsItemCodes, $additionalItemsKeys);
+         $motivationValueGroups = [];
+         foreach (range(1, 5) as $ind) {
+             $motivationValueGroups []= MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one();
+         }
+         /** @var $motivationValueGroups MotivationValueGroup[] */
+         $motivationValueGroupForecast = MotivationValueGroup::find()->where(['alias' => 'forecast'])->one();
+         /** @var $motivationValueGroupForecast MotivationValueGroup */
+         $motivation = Motivation::find()->where(['store_id' => $store_id, 'year' => $year, 'month' => $month])->one();
+         foreach ($items as $code) {
+             if ($motivation) {
+                 $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivation->id,
+                     'motivation_group_id' => $motivationValueGroupForecast->id, 'value_id' => $code])->one();
+                 $sum = 0;
+                 $sum_type = MotivationCostsItem::DATA_TYPE_INT;
+                 foreach (range(1, 5) as $ind) {
+                     $mv = MotivationValue::find()->where(['motivation_id' => $motivation->id,
+                         'motivation_group_id' => $motivationValueGroups[$ind-1]->id, 'value_id' => $code])->one();
+                     /** @var $mv MotivationValue */
+                     if ($mv) {
+                         switch ($mv->value_type) {
+                             case MotivationCostsItem::DATA_TYPE_INT: { $sum += $mv->value_int; break; }
+                             case MotivationCostsItem::DATA_TYPE_FLOAT: { $sum += $mv->value_float; $sum_type = MotivationCostsItem::DATA_TYPE_FLOAT; break; }
+                             case MotivationCostsItem::DATA_TYPE_STRING: { throw new \Exception(Json::encode("Тип string невозможен для вычисления прогноза")); }
+                         };
+                     }
+                 }
+                 if (!$motivationValue) {
+                     $motivationValue = new MotivationValue;
+                     $motivationValue->motivation_id = $motivation->id;
+                     $motivationValue->motivation_group_id = $motivationValueGroupForecast->id;
+                     $motivationValue->value_id = $code;
+                 }
+                 $motivationValue->value_type = $sum_type;
+                 switch ($sum_type) {
+                     case MotivationCostsItem::DATA_TYPE_INT: { $motivationValue->value_int = $sum; break; }
+                     default: { $motivationValue->value_float = $sum; break; }
+                 }
+                 $motivationValue->save();
+                 if ($motivationValue->getErrors()) {
+                     throw new \Exception(Json::encode($motivationValue->getErrors()));
+                 }
+             }
+         }
+     }
+     /**
+      * Получение записей по фактическому количеству смен в магазине за указанный период.
+      *
+      * @param string $startDate Дата начала периода в формате 'YYYY-MM-DD'.
+      * @param string $endDate Дата окончания периода в формате 'YYYY-MM-DD'.
+      * @param int $storeId Идентификатор магазина.
+      *
+      * @return array|null Возвращает массив записей из модели TimetableFactModel, соответствующих условиям,
+      *                    или null, если записи не найдены.
+      */
+     public static function getTimetableFactRecordsByDateAndStore($startDate, $endDate, $storeId)
+     {
+         // Делаем запрос к TimetableFactModel
+         $records = TimetableFactModel::find()
+             ->where(['store_id' => $storeId])
+             ->andWhere(['between', 'date_shift', $startDate, $endDate])
+             ->all();
+         return $records;
+     }
+     /**
+      * Получение суммы отпускных для магазина за определенный период.
+      *
+      * Этот метод выполняет поиск записей в таблице Timetable с `slot_type_id` равным 2,
+      * которые соответствуют указанному магазину и попадают в заданный период.
+      * Для каждой записи находится самая последняя запись о платеже сотрудника,
+      * и её значение используется для расчета суммы отпускных.
+      *
+      * @param string $startDate Дата начала периода в формате 'YYYY-MM-DD'.
+      * @param string $endDate Дата окончания периода в формате 'YYYY-MM-DD'.
+      * @param int $storeId Идентификатор магазина.
+      *
+      * @return float Возвращает общую сумму отпускных за указанный период.
+      *               Если записей не найдено, возвращается 0.0.
+      */
+     public static function getVacationsSum($startDate, $endDate, $storeId):float
+     {
+         // Делаем запрос к таблице Timetable для получения записей с slot_type_id = 2 (Timetable::TIMESLOT_VACATION)
+         $records = Timetable::find()
+             ->where(['store_id' => $storeId])
+             ->andWhere(['between', 'date', $startDate, $endDate])
+             ->andWhere(['slot_type_id' => Timetable::TIMESLOT_VACATION])
+             ->all();
+         // Проверяем, есть ли записи
+         if (empty($records)) {
+             return 0.0; // Возвращаем 0, если записей нет
+         }
+         $dailyPayments = self::getEmployeePayments($endDate);
+         $vacationsSum = 0.0;
+         foreach ($records as $record) {
+             $dailyPayment = isset($dailyPayments[$record->admin_id]) ? $dailyPayments[$record->admin_id] : 0.0;
+             $vacationsSum += $dailyPayment;
+         }
+         return $vacationsSum;
+     }
+     /**
+      * Получение суммы всех недель для определенного `value_id` из таблицы `motivation_value`.
+      *
+      * Метод выполняет запрос к модели `MotivationValue`, чтобы получить все записи,
+      * соответствующие указанным `motivation_id` и `value_id`, и принадлежащие одной из
+      * определенных групп мотивации. Затем метод суммирует значения поля `value_float`
+      * из всех полученных записей.
+      *
+      * @param int $id Идентификатор мотивации (`motivation_id`).
+      * @param int $valueId Идентификатор значения (`value_id`).
+      *
+      * @return float Возвращает сумму всех значений `value_float` для заданного `value_id`.
+      *               Если записи не найдены, возвращается 0.
+      */
+     public static function getMonthSum($id, $valueId)
+     {
+         $aliases = ['week1', 'week2', 'week3', 'week4', 'week5'];
+         $groupIds = MotivationCostsItem::find()
+             ->select('id')
+             ->where(['alias' => $aliases])
+             ->column();
+         // Запрос к модели MotivationValue для получения записей с заданными условиями
+         $records = MotivationValue::find()
+             ->where(['motivation_id' => $id, 'value_id' => $valueId])
+             ->andWhere(['in', 'motivation_group_id', $groupIds])
+             ->all();
+         // Получаем массив значений value_float
+         $values = array_map(function($record) {
+             return $record->value_float;
+         }, $records);
+         // Суммируем значения value_float
+         $totalSum = array_sum($values);
+         return $totalSum;
+     }
+     /**
+      * Создает или обновляет запись в таблице `motivation_value`.
+      *
+      * Метод пытается найти существующую запись в таблице `motivation_value` по заданным
+      * `motivation_id`, `group_id` и `value_id`. Если запись не найдена, создается новая.
+      * В зависимости от типа значения (`value_type`), метод устанавливает соответствующее поле
+      * (`value_float`, `value_int`, `value_string`) и сохраняет запись.
+      * Если сохранение успешно, возвращается ID записи. В случае ошибки возвращается `false`.
+      *
+      * @param int $motivationId Идентификатор мотивации.
+      * @param string $groupAlias Алиас группы мотивации.
+      * @param int $valueId Идентификатор значения.
+      * @param string $valueType Тип значения, который может быть 'float', 'int' или 'string'.
+      * @param string $value_string Значение, которое нужно сохранить (string).
+      * @param int $value_int Значение, которое нужно сохранить (int).
+      * @param float $value_float Значение, которое нужно сохранить (float).
+      *
+      * @return int|false Возвращает ID сохраненной или обновленной записи, либо `false` в случае ошибки.
+      * @throws \InvalidArgumentException Если передан некорректный тип значения.
+      */
+     public static function saveOrUpdateMotivationValue($motivationId, $groupAlias, $valueId, $valueType, $value)
+     {
+         // Найти id по алиасу в таблице MotivationValueGroup
+         $group = MotivationValueGroup::findOne(['alias' => $groupAlias]);
+         if ($group === null) {
+             throw new \InvalidArgumentException("Неверный алиас группы: $groupAlias");
+         }
+         $groupId = $group->id;
+         // Найти существующую запись
+         $motivationValue = MotivationValue::findOne([
+             'motivation_id' => $motivationId,
+             'motivation_group_id' => $groupId,
+             'value_id' => $valueId,
+         ]);
+         // Если запись не найдена, создать новую
+         if ($motivationValue === null) {
+             $motivationValue = new MotivationValue();
+             $motivationValue->motivation_id = $motivationId;
+             $motivationValue->motivation_group_id = $groupId;
+             $motivationValue->value_id = $valueId;
+             $motivationValue->value_type = $valueType;
+         }
+         // Установить значение в зависимости от типа
+         switch ($valueType) {
+             case 'float':
+                 $motivationValue->value_float = $value;
+                 break;
+             case 'int':
+                 $motivationValue->value_int = $value;
+                 break;
+             case 'string':
+                 $motivationValue->value_string = $value;
+                 break;
+             default:
+                 throw new \InvalidArgumentException("Неправильное значение типа: $valueType");
+         }
+         // Сохранить запись и вернуть id или false
+         if ($motivationValue->save()) {
+             return $motivationValue->id;
+         } else {
+             Yii::error("Не удалось сохранить значение: " . json_encode($motivationValue->errors));
+             return false;
+         }
+     }
+     /**
+      * Определяет номер недели в месяце для указанной даты.
+      *
+      * Метод вычисляет номер недели в месяце на основе дня месяца, переданного в объекте `DateTime`.
+      * Недели считаются следующим образом:
+      * - 1-я неделя: с 1-го по 7-й день месяца.
+      * - 2-я неделя: с 8-го по 14-й день месяца.
+      * - 3-я неделя: с 15-го по 21-й день месяца.
+      * - 4-я неделя: с 22-го по 28-й день месяца.
+      * - 5-я неделя: с 29-го дня месяца и до конца месяца.
+      *
+      * @param string $date Строка даты в формате 'Y-m-d', для которой нужно определить номер недели.
+      *
+      * @return int Номер недели в месяце (от 1 до 5).
+      */
+     public static function getWeekOfMonth($date)
+     {
+         // Преобразуем строку даты в день месяца
+         $dayOfMonth = intval(date('j', strtotime($date)));
+         // Определяем номер недели в месяце
+         if ($dayOfMonth <= 7) {
+             return 1;
+         } elseif ($dayOfMonth <= 14) {
+             return 2;
+         } elseif ($dayOfMonth <= 21) {
+             return 3;
+         } elseif ($dayOfMonth <= 28) {
+             return 4;
+         } else {
+             return 5;
+         }
+     }
+     /**
+      * Возвращает дату начала указанной недели месяца для заданной даты.
+      *
+      * Метод определяет начало недели в месяце на основе номера недели, переданного в параметре `$weekOfMonth`.
+      * Возвращается объект `DateTime`, представляющий первый день этой недели. Если номер недели выходит за пределы
+      * допустимых значений (1-5), по умолчанию возвращается начало первой недели месяца.
+      *
+      * @param string $date Строка даты в формате 'Y-m-d', представляющая дату, для которой определяется неделя.
+      * @param int $weekOfMonth Номер недели в месяце (от 1 до 5).
+      *
+      * @return string Дата начала указанной недели месяца в формате 'Y-m-d'.
+      */
+     public static function getStartOfWeek($date, $weekOfMonth)
+     {
+         // Извлекаем год и месяц из строки даты
+         $year = date('Y', strtotime($date));
+         $month = date('m', strtotime($date));
+         switch ($weekOfMonth) {
+             case 1:
+                 return sprintf("%s-%s-01", $year, $month);
+             case 2:
+                 return sprintf("%s-%s-08", $year, $month);
+             case 3:
+                 return sprintf("%s-%s-15", $year, $month);
+             case 4:
+                 return sprintf("%s-%s-22", $year, $month);
+             case 5:
+                 return sprintf("%s-%s-29", $year, $month);
+             default:
+                 throw new \InvalidArgumentException("Некорректное значение для недели месяца: $weekOfMonth");
+         }
+     }
+     /**
+      * Вычисление общей суммы фонда оплаты труда (ФОТ) - зарплаты и отпускных для магазина за указанный период.
+      *
+      * Этот метод сначала получает записи о сменах сотрудников за указанный период и сумму отпускных.
+      * Затем для каждой записи определяется соответствующая дневная зарплата, и она добавляется к общей сумме.
+      * Если в записи уже указана зарплата за смену, она используется вместо дневной зарплаты.
+      * В конце к общей сумме зарплат добавляется сумма отпускных.
+      *
+      * @param string $startDate Дата начала периода в формате 'YYYY-MM-DD'.
+      * @param string $endDate Дата окончания периода в формате 'YYYY-MM-DD'.
+      * @param int $storeId Идентификатор магазина.
+      *
+      * @return float Возвращает общую сумму фонда оплаты труда за указанный период,
+      *               включая зарплаты и отпускные.
+      */
+     public static function calculateTotalSalary($startDate, $endDate, $storeId):float
+     {
+         $records = self::getTimetableFactRecordsByDateAndStore($startDate, $endDate, $storeId);
+         $vacationSum = self::getVacationsSum($startDate, $endDate, $storeId);
+         $dailyPayments = self::getEmployeePayments($endDate);
+         $totalSalary = 0.0;
+         foreach ($records as $record) {
+             $dailyPayment = isset($dailyPayments[$record->admin_id]) ? $dailyPayments[$record->admin_id] : 0.0;
+             if (!empty($record->salary_shift)) {
+                 $totalSalary += $record->salary_shift;
+             } else {
+                 $totalSalary += $dailyPayment;
+             }
+         }
+         return $totalSalary + $vacationSum;
+     }
+     /**
+      * @param $currentDate
+      * @return array
+      */
+     public static function getEmployeePayments($currentDate): array
+     {
+         // Запрос для получения всех записей, но только с учетом последней даты для каждого admin_id
+         $employeePayments = EmployeePayment::find()
+             ->where(['<=', 'date', $currentDate])
+             ->orderBy(['admin_id' => SORT_ASC, 'date' => SORT_DESC])
+             ->all();
+         // Преобразование результатов в массив admin_id => daily_payment
+         $dailyPayments = [];
+         foreach ($employeePayments as $payment) {
+             if (!isset($dailyPayments[$payment->admin_id])) {
+                 $dailyPayments[$payment->admin_id] = $payment->daily_payment;
+             }
+         }
+         return $dailyPayments;
+     }
+     /**
+      * Возвращает массив, представляющий недели месяца (с указаниме начала и конца недели) для указанного года и месяца.
+      *
+      * @param int|string $year  Год в виде числа или строки (например, `2024` или `'2024'`).
+      * @param int|string $month Месяц в виде числа или строки (например, `2` или `'02'`).
+      *
+      * @return array Массив недель месяца, где каждая неделя представлена ассоциативным массивом с ключами 'start' и 'end'.
+      *               Пример возвращаемого массива:
+      *               [
+      *                   ['start' => 1, 'end' => 7],
+      *                   ['start' => 8, 'end' => 14],
+      *                   ['start' => 15, 'end' => 21],
+      *                   ['start' => 22, 'end' => 28],
+      *                   ['start' => 29, 'end' => 30] // если месяц имеет больше 28 дней
+      *               ]
+      */
+     public static function getWeeksOfMonthArray($year, $month): array
+     {
+         // Массив недель, изначально включающий первые четыре недели
+         $weeks = [
+             ['start' => 1, 'end' => 7],
+             ['start' => 8, 'end' => 14],
+             ['start' => 15, 'end' => 21],
+             ['start' => 22, 'end' => 28],
+         ];
+         // Определяем последний день месяца
+         $lastDayOfMonth = date('t', strtotime("$year-$month-01"));
+         // Если в месяце больше 28 дней, добавляем пятую неделю
+         if ($lastDayOfMonth > 28) {
+             $weeks[] = ['start' => 29, 'end' => $lastDayOfMonth];
+         }
+         return $weeks;
+     }
++
 +    public static function calculateFactFormula(&$motivationDataTableSort) {
 +        $indMap = [];
 +        foreach ($motivationDataTableSort as $ind => $row) {
 +            if (!key_exists('name', $row)) {
 +                continue;
 +            }
 +            $indMap[$row['name']] = $ind;
 +        }
 +        foreach (range(0,7) as $ind) {
 +            switch ($ind) {
 +                case 0: { $column = 'plan'; break; }
 +                case 6: { $column = 'fact'; break; }
 +                case 7: { $column = 'forecast'; break; }
 +                default: { $column = 'week' . $ind; break; }
 +            }
 +
 +            $motivationDataTableSort[$indMap["Продажа товара"]][$column] =
 +                $motivationDataTableSort[$indMap["Оффлайн продажи"]][$column] +
 +                $motivationDataTableSort[$indMap["Онлайн продажи"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Прочие услуги"]][$column] =
 +                $motivationDataTableSort[$indMap["Услуги по сборке"]][$column] +
 +                $motivationDataTableSort[$indMap["Услуги по доставке"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Выручка от реализации"]][$column] =
 +                $motivationDataTableSort[$indMap["Продажа товара"]][$column] +
 +                $motivationDataTableSort[$indMap["Прочие услуги"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Услуги агентов (Расходы на закупку, хранение, доставку товара)"]][$column] =
 +                (
 +                    $motivationDataTableSort[$indMap["Стоимость товара"]][$column] +
 +                    $motivationDataTableSort[$indMap["Брак, пересорт"]][$column]
 +                ) *
 +                $motivationDataTableSort[$indMap["Услуги агентов (тариф)"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Брак, пересорт"]][$column] =
 +                $motivationDataTableSort[$indMap["Брак с поставки"]][$column] +
 +                $motivationDataTableSort[$indMap["Списание неликвидного товара: порча, истечение срока годности"]][$column] +
 +                $motivationDataTableSort[$indMap["Брак из-за поломки оборудования"]][$column] +
 +                $motivationDataTableSort[$indMap["Пересорт"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Прямые расходы на продажу"]][$column] =
 +                $motivationDataTableSort[$indMap["Стоимость товара"]][$column] +
 +                $motivationDataTableSort[$indMap["Услуги агентов (Расходы на закупку, хранение, доставку товара)"]][$column] +
 +                $motivationDataTableSort[$indMap["Брак, пересорт"]][$column] +
 +                $motivationDataTableSort[$indMap["Расходные материалы (обеспечение продаж)"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Маржинальный доход"]][$column] =
 +                $motivationDataTableSort[$indMap["Выручка от реализации"]][$column] -
 +                $motivationDataTableSort[$indMap["Прямые расходы на продажу"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Оплата труда"]][$column] =
 +                $motivationDataTableSort[$indMap["Фонд оплаты труда персонала"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Содержание помещения"]][$column] =
 +                $motivationDataTableSort[$indMap["Аренда"]][$column] +
 +                $motivationDataTableSort[$indMap["Коммунальные услуги"]][$column] +
 +                $motivationDataTableSort[$indMap["Охрана"]][$column] +
 +                $motivationDataTableSort[$indMap["Услуги по уборке помещений и территории"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Расходы по доставке"]][$column] =
 +                $motivationDataTableSort[$indMap["Доставка до клиента курьер"]][$column] +
 +                $motivationDataTableSort[$indMap["Доставка до клиента такси"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Содержание и обслуживание ОС и НМА"]][$column] =
 +                $motivationDataTableSort[$indMap["Холодильное оборудование (ремонт, содержание, ТО)"]][$column] +
 +                $motivationDataTableSort[$indMap["Расходы на содержание и ремонт оргтехники, в т.ч. расходные материалы"]][$column] +
 +                $motivationDataTableSort[$indMap["Расходы на содержание и ремонт прочих ОС"]][$column] +
 +                $motivationDataTableSort[$indMap["Техническое обслуживание кассовых аппаратов"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Услуги связи"]][$column] =
 +                $motivationDataTableSort[$indMap["Интернет"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Прочие операционные расходы"]][$column] =
 +                $motivationDataTableSort[$indMap["Хозяйственные товары"]][$column] +
 +                $motivationDataTableSort[$indMap["Канцтовары"]][$column] +
 +                $motivationDataTableSort[$indMap["Вода питьевая"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Операционные расходы (Себестоимость)"]][$column] =
 +                $motivationDataTableSort[$indMap["Оплата труда"]][$column] +
 +                $motivationDataTableSort[$indMap["Содержание помещения"]][$column] +
 +                $motivationDataTableSort[$indMap["Расходы по доставке"]][$column] +
 +                $motivationDataTableSort[$indMap["Услуги маркетплейсов"]][$column] +
 +                $motivationDataTableSort[$indMap["Содержание и обслуживание ОС и НМА"]][$column] +
 +                $motivationDataTableSort[$indMap["Услуги связи"]][$column] +
 +                $motivationDataTableSort[$indMap["Прочие операционные расходы"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Валовая прибыль"]][$column] =
 +                $motivationDataTableSort[$indMap["Маржинальный доход"]][$column] -
 +                $motivationDataTableSort[$indMap["Операционные расходы (Себестоимость)"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Бухгалтерия и финансы"]][$column] =
 +                $motivationDataTableSort[$indMap["Бухгалтерские услуги: постановка и ведение БУ и НУ"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Юридическое сопровождение"]][$column] =
 +                $motivationDataTableSort[$indMap["Юридические услуги"]][$column];
 +
 +            $motivationDataTableSort[$indMap["HR- услуги"]][$column] =
 +                $motivationDataTableSort[$indMap["Кадровое администрирование, охрана труда"]][$column] +
 +                $motivationDataTableSort[$indMap["Услуги по подбору персонала"]][$column];
 +
 +            $motivationDataTableSort[$indMap["IT услуги"]][$column] =
 +                $motivationDataTableSort[$indMap["Администрирование ИТ инфраструктуры (подключения к базам данных, ПО, почта, интернет)"]][$column] +
 +                $motivationDataTableSort[$indMap["Лицензия на ПО: ERP система"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Общехозяйственные расходы"]][$column] =
 +                $motivationDataTableSort[$indMap["Бухгалтерия и финансы"]][$column] +
 +                $motivationDataTableSort[$indMap["Юридическое сопровождение"]][$column] +
 +                $motivationDataTableSort[$indMap["HR- услуги"]][$column] +
 +                $motivationDataTableSort[$indMap["IT услуги"]][$column] +
 +                $motivationDataTableSort[$indMap["Продвижение и продажа товара через сайт"]][$column];
 +
 +            $motivationDataTableSort[$indMap["Чистая прибыль"]][$column] =
 +                $motivationDataTableSort[$indMap["Валовая прибыль"]][$column] -
 +                $motivationDataTableSort[$indMap["Общехозяйственные расходы"]][$column];
 +
 +            $c5 = $motivationDataTableSort[$indMap["Выручка от реализации"]][$column];
 +            if ($c5 != 0) {
 +                $motivationDataTableSort[$indMap["Рентабельность по чистой прибыли, %"]][$column] =
 +                    $motivationDataTableSort[$indMap["Чистая прибыль"]][$column] / $c5;
 +            }
 +
 +            if ($ind > 0) {
 +                $c62 = $motivationDataTableSort[$indMap["Чистая прибыль"]][$column];
 +                $b64 = $motivationDataTableSort[$indMap["Минимальный порог Чистой прибыли, руб."]][$ind == 6 ? "plan" : $column];
 +
 +                $j66 = 0;
 +                if ($c62 >= $b64) {
 +                    $formula = $c62 - $c5 * $motivationDataTableSort[$indMap["Пороговый коэффициент"]]["plan"];
 +                    if ($formula > 0) {
 +                        $j66 = $formula;
 +                    }
 +                }
 +                $motivationDataTableSort[$indMap["Расчет премии"]][$column] = $j66;
 +            }
 +        }
 +
 +        // Отклонение
 +        $deviationFunc = function ($name) use(&$motivationDataTableSort, &$indMap) {
 +            if ($motivationDataTableSort[$indMap[$name]]["plan"] != 0) {
 +                $motivationDataTableSort[$indMap[$name]]["deviation"] =
 +                    $motivationDataTableSort[$indMap[$name]]["fact"] /
 +                    $motivationDataTableSort[$indMap[$name]]["plan"];
 +            }
 +        };
 +        $names = [
 +            "Выручка от реализации",
 +            "Продажа товара",
 +            "Оффлайн продажи",
 +            "Онлайн продажи",
 +            "Прочие услуги",
 +            "Услуги по сборке",
 +            "Услуги по доставке",
 +            "Прямые расходы на продажу",
 +            "Стоимость товара",
 +            "Услуги агентов (Расходы на закупку, хранение, доставку товара)",
 +            "Брак, пересорт",
 +            "Брак с поставки",
 +            "Списание неликвидного товара: порча, истечение срока годности",
 +            "Брак из-за поломки оборудования",
 +            "Пересорт",
 +            "Расходные материалы (обеспечение продаж)",
 +            "Маржинальный доход",
 +            "Операционные расходы (Себестоимость)",
 +            "Оплата труда",
 +            "Фонд оплаты труда персонала",
 +            "Содержание помещения",
 +            "Аренда",
 +            "Коммунальные услуги",
 +            "Охрана",
 +            "Услуги по уборке помещений и территории",
 +            "Расходы по доставке",
 +            "Доставка до клиента курьер",
 +            "Доставка до клиента такси",
 +            "Услуги маркетплейсов",
 +            "Содержание и обслуживание ОС и НМА",
 +            "Холодильное оборудование (ремонт, содержание, ТО)",
 +            "Расходы на содержание и ремонт оргтехники, в т.ч. расходные материалы",
 +            "Расходы на содержание и ремонт прочих ОС",
 +            "Техническое обслуживание кассовых аппаратов",
 +            "Услуги связи",
 +            "Интернет",
 +            "Прочие операционные расходы",
 +            "Хозяйственные товары",
 +            "Канцтовары",
 +            "Вода питьевая",
 +            "Валовая прибыль",
 +            "Общехозяйственные расходы",
 +            "Бухгалтерия и финансы",
 +            "Бухгалтерские услуги: постановка и ведение БУ и НУ",
 +            "Юридическое сопровождение",
 +            "Юридические услуги",
 +            "HR- услуги",
 +            "Кадровое администрирование, охрана труда",
 +            "Услуги по подбору персонала",
 +            "IT услуги",
 +            "Администрирование ИТ инфраструктуры (подключения к базам данных, ПО, почта, интернет)",
 +            "Лицензия на ПО: ERP система",
 +            "Продвижение и продажа товара через сайт",
 +            "Чистая прибыль",
 +            "Рентабельность по чистой прибыли, %"
 +        ];
 +        foreach ($names as $name) {
 +            $deviationFunc($name);
 +        }
 +    }
  }