]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Merge branch 'develop' into feature_fomichev_erp-91_create_method_sum_salary
authorJoySystem_v <fvv2011@gmail.com>
Fri, 16 Aug 2024 07:04:01 +0000 (10:04 +0300)
committerJoySystem_v <fvv2011@gmail.com>
Fri, 16 Aug 2024 07:04:01 +0000 (10:04 +0300)
# Conflicts:
# erp24/services/MotivationService.php

1  2 
erp24/services/MotivationService.php

index 95dc0ea9e14f9936b66b679b2b76ad2a2ddf542f,3d358fc4662e46aff452185d528c2fa43f94e51f..24a4766c72da371737f7b277e552f7e520ef4263
@@@ -28,17 -20,35 +28,46 @@@ use yii_app\records\EmployeePayment
  
  class MotivationService
  {
+     // 6. Создание массива дополнительных элементов
+     static $additionalItems = [
+             80 => ['name' => 'Выручка от реализации'],
+             90 => ['name' => 'Продажа товара'],
+             115 => ['name' => 'Прочие услуги'],
+             135 => ['name' => 'Прямые расходы на продажу'],
+             143 => ['name' => 'Услуги агентов (Расходы на закупку, хранение, доставку товара)'],
+             146 => ['name' => 'Брак, пересорт'],
+             192 => ['name' => 'Маржинальный доход'],
+             194 => ['name' => 'Операционные расходы (Себестоимость)'],
+             196 => ['name' => 'Оплата труда'],
+             205 => ['name' => 'Содержание помещения'],
+             245 => ['name' => 'Расходы по доставке'],
+             275 => ['name' => 'Содержание и обслуживание ОС и НМА'],
+             315 => ['name' => 'Услуги связи'],
+             325 => ['name' => 'Прочие операционные расходы'],
+             353 => ['name' => 'Валовая прибыль'],
+             355 => ['name' => 'Общехозяйственные расходы'],
+             357 => ['name' => 'Бухгалтерия и финансы'],
+             365 => ['name' => 'Юридическое сопровождение'],
+             375 => ['name' => 'HR- услуги'],
+             395 => ['name' => 'IT услуги'],
+             425 => ['name' => 'Чистая прибыль'],
+             427 => ['name' => 'Рентабельность по чистой прибыли, %'],
+             428 => ['name' => 'Минимальный порог Чистой прибыли, руб.'],
+             435 => ['name' => 'Расчет премии']
+         ];
 +// Код из таблицы MotivationCostsItem строки - Фонд оплаты труда персонала
 +    const MCI_FOT = 11;
 +    /**
 +     * Получает ассоциативный массив алиасов групп мотивации.
 +     *
 +     * Этот метод извлекает все записи из таблицы `MotivationValueGroup`, а затем создает
 +     * ассоциативный массив, где ключами являются идентификаторы групп, а значениями — их алиасы.
 +     *
 +     * @return array Ассоциативный массив, где ключами являются ID групп мотивации,
 +     *               а значениями — их алиасы.
 +     */
      private function getMotivationValueGroupAliases()
      {
          $groups = MotivationValueGroup::find()->all();
          }
      }
  
+     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;
 +    }
  }