]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
[ERP-140] Добавлен подсчёт зарплаты в факт
authorAlexander Smirnov <fredeom@mail.ru>
Wed, 14 Aug 2024 12:31:48 +0000 (15:31 +0300)
committerAlexander Smirnov <fredeom@mail.ru>
Wed, 14 Aug 2024 12:31:48 +0000 (15:31 +0300)
erp24/actions/motivation/TestFactAction.php
erp24/scripts/tasks/task_32_motivation_fact.php
erp24/services/MotivationService.php

index 87f915e46d8b8e9fb6a3ed3a23a2aafe516ab987..97ba3397f7ca6eb4ece848b7d097cd5a3d472b96 100644 (file)
@@ -55,6 +55,8 @@ class TestFactAction extends Action
             MotivationService::calculateMonthServices($model->year, $model->month);
 
             MotivationService::calculateMonthDefect($model->year, $model->month);
+
+            MotivationService::calculateMonthSalary($model->year, $model->month);
         }
 
         return $this->controller->render('test-fact', compact('model', 'years', 'months'));
index 334f46cf4cdc7aacddfd1d0d016c2210a9c21928..496647a048f0049787b673349dc13013d610dcba 100644 (file)
@@ -59,6 +59,7 @@ try {
 
         MotivationService::calculateMonthDefect($year, $month);
 
+        MotivationService::calculateMonthSalary($year, $month);
 
         //////////////////////////////////////////////
 
index 5e499e6da99213b51c456286fa587758bc14528f..b18e0541ca05fc461b89930273f64093a7b63c23 100644 (file)
@@ -2,8 +2,10 @@
 
 namespace yii_app\services;
 
+use Yii;
 use PhpOffice\PhpSpreadsheet\IOFactory;
 use yii\helpers\Json;
+use yii_app\records\EmployeePayment;
 use yii_app\records\ExportImportTable;
 use yii\helpers\ArrayHelper;
 use yii_app\records\Motivation;
@@ -11,6 +13,8 @@ use yii_app\records\MotivationValue;
 use yii_app\records\MotivationValueGroup;
 use yii_app\records\CityStore;
 use yii_app\records\MotivationCostsItem;
+use yii_app\records\Timetable;
+use yii_app\records\TimetableFactModel;
 use yii_app\records\WriteOffs;
 use yii_app\records\Products1c;
 use yii_app\records\ProductsClass;
@@ -21,7 +25,15 @@ use yii_app\records\SalesProducts;
 class MotivationService
 {
 
-
+    /**
+     * Получает ассоциативный массив алиасов групп мотивации.
+     *
+     * Этот метод извлекает все записи из таблицы `MotivationValueGroup`, а затем создает
+     * ассоциативный массив, где ключами являются идентификаторы групп, а значениями — их алиасы.
+     *
+     * @return array Ассоциативный массив, где ключами являются ID групп мотивации,
+     *               а значениями — их алиасы.
+     */
     private function getMotivationValueGroupAliases()
     {
         $groups = MotivationValueGroup::find()->all();
@@ -32,7 +44,20 @@ class MotivationService
         return $aliases;
     }
 
-
+    /**
+     * Получает отсортированные данные мотивации для заданного магазина, года и месяца.
+     *
+     * Метод извлекает мотивационные данные, соответствующие указанному магазину, году и месяцу,
+     * а затем обрабатывает их для создания структурированного и отсортированного массива значений.
+     * Включает дополнительные элементы с нулевыми значениями для плановых и фактических данных,
+     * если они отсутствуют в основной таблице мотивации.
+     *
+     * @param int|null $storeId Идентификатор магазина. Если не указан, может использоваться значение по умолчанию.
+     * @param int|null $year Год. Если не указан, может использоваться значение по умолчанию.
+     * @param int|null $month Месяц. Если не указан, может использоваться значение по умолчанию.
+     *
+     * @return array Отсортированный массив данных мотивации, включая плановые, фактические и другие элементы.
+     */
     public function getMotivationDataTableSort($storeId = null, $year = null, $month = null)
     {
         // 1. Запрос к таблице Motivation
@@ -520,6 +545,292 @@ class MotivationService
         }
     }
 
+    /**
+     * Получение записей по фактическому количеству смен в магазине за указанный период.
+     *
+     * @param string $startDate Дата начала периода в формате 'YYYY-MM-DD'.
+     * @param string $endDate Дата окончания периода в формате 'YYYY-MM-DD'.
+     * @param int $store_id Идентификатор магазина.
+     *
+     * @return array|null Возвращает массив записей из модели TimetableFactModel, соответствующих условиям,
+     *                    или null, если записи не найдены.
+     */
+    public static function getTimetableFactRecordsByDateAndStore($startDate, $endDate, $store_id)
+    {
+
+
+        // Делаем запрос к TimetableFactModel
+        $records = TimetableFactModel::find()
+            ->where(['store_id' => $store_id])
+            ->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 $store_id Идентификатор магазина.
+     *
+     * @return float Возвращает общую сумму отпускных за указанный период.
+     *               Если записей не найдено, возвращается 0.0.
+     */
+    public static function getVacationsSum($startDate, $endDate, $store_id):float
+    {
+        // Делаем запрос к таблице Timetable для получения записей с slot_type_id = 2
+        $records = Timetable::find()
+
+            ->where(['store_id' => $store_id])
+            ->andWhere(['between', 'date', $startDate, $endDate])
+            ->andWhere(['slot_type_id' => 2])
+            ->all();
+
+        // Проверяем, есть ли записи
+        if (empty($records)) {
+            return 0.0; // Возвращаем 0, если записей нет
+        }
+
+
+
+        $vacationsSum = 0.0;
+        foreach ($records as $record) {
+            // Находим самую позднюю запись по admin_id
+            $payment = EmployeePayment::find()
+                ->where(['admin_id' => $record->admin_id])
+                ->orderBy(['date' => SORT_DESC])
+                ->one();
+            $dailyPayment = $payment ? $payment->daily_payment : 0.0;
+
+
+            $vacationsSum += $dailyPayment;
+
+        }
+
+
+
+        return $vacationsSum;
+    }
+
+    /**
+     * Получение суммы всех недель для определенного `value_id` из таблицы `motivation_value`.
+     *
+     * Метод выполняет запрос к модели `MotivationValue`, чтобы получить все записи,
+     * соответствующие указанным `motivation_id` и `value_id`, и принадлежащие одной из
+     * определенных групп мотивации. Затем метод суммирует значения поля `value_float`
+     * из всех полученных записей.
+     *
+     * @param int $id Идентификатор мотивации (`motivation_id`).
+     * @param int $value_id Идентификатор значения (`value_id`).
+     *
+     * @return float Возвращает сумму всех значений `value_float` для заданного `value_id`.
+     *               Если записи не найдены, возвращается 0.
+     */
+    public static function getMonthSum($id, $value_id)
+    {
+        // Запрос к модели MotivationValue для получения записей с заданными условиями
+        $records = MotivationValue::find()
+            ->where(['motivation_id' => $id, 'value_id' => $value_id])
+            ->andWhere(['in', 'motivation_group_id', [1, 2, 3, 4, 5]])
+            ->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 $motivation_id Идентификатор мотивации.
+     * @param int $group_id Идентификатор группы мотивации.
+     * @param int $value_id Идентификатор значения.
+     * @param string $value_type Тип значения, который может быть '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($motivation_id, $group_id, $value_id, $value_type, $value)
+    {
+        // Найти существующую запись
+        $motivationValue = MotivationValue::findOne([
+            'motivation_id' => $motivation_id,
+            'motivation_group_id' => $group_id,
+            'value_id' => $value_id,
+        ]);
+
+        // Если запись не найдена, создать новую
+        if ($motivationValue === null) {
+            $motivationValue = new MotivationValue();
+            $motivationValue->motivation_id = $motivation_id;
+            $motivationValue->motivation_group_id = $group_id;
+            $motivationValue->value_id = $value_id;
+            $motivationValue->value_type = $value_type;
+        }
+
+        // Установить значение в зависимости от типа
+        switch ($value_type) {
+            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("Неправильное значение типа: $value_type");
+        }
+
+        // Сохранить запись и вернуть id или false
+        if ($motivationValue->save()) {
+            return $motivationValue->id;
+        } else {
+            Yii::error("Не удалось сохранить значение: " . json_encode($motivationValue->errors));
+            return false;
+        }
+    }
+
+    public static function getMotivationValue($motivation_id, $group_id, $value_id) {
+        $value = 0;
+        $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivation_id,
+            'motivation_group_id' => $group_id, 'value_id' => $value_id])->one();
+        /** @var $motivationValue MotivationValue */
+        if ($motivationValue) {
+            switch ($motivationValue->value_type) {
+                case MotivationCostsItem::DATA_TYPE_INT: { $value = $motivationValue->value_int; break; }
+                default: { $value = $motivationValue->value_float; break;}
+            }
+        }
+        return $value;
+    }
+
+    /**
+     * Определяет номер недели в месяце для указанной даты.
+     *
+     * Метод вычисляет номер недели в месяце на основе дня месяца, переданного в объекте `DateTime`.
+     * Недели считаются следующим образом:
+     * - 1-я неделя: с 1-го по 7-й день месяца.
+     * - 2-я неделя: с 8-го по 14-й день месяца.
+     * - 3-я неделя: с 15-го по 21-й день месяца.
+     * - 4-я неделя: с 22-го по 28-й день месяца.
+     * - 5-я неделя: с 29-го дня месяца и до конца месяца.
+     *
+     * @param \DateTime $dateTime Объект `DateTime`, для которого нужно определить номер недели.
+     *
+     * @return int Номер недели в месяце (от 1 до 5).
+     */
+    public static function getWeekOfMonth($dateTime)
+    {
+        $dayOfMonth = intval($dateTime->format('j'));
+        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 \DateTime $dateTime Объект `DateTime`, представляющий дату, для которой определяется неделя.
+     * @param int $weekOfMonth Номер недели в месяце (от 1 до 5).
+     *
+     * @return \DateTime Дата начала указанной недели месяца.
+     */
+    public static function getStartOfWeek($dateTime, $weekOfMonth)
+    {
+        $year = $dateTime->format('Y');
+        $month = $dateTime->format('m');
+
+        switch ($weekOfMonth) {
+            case 1:
+                return new DateTime("$year-$month-01");
+            case 2:
+                return new DateTime("$year-$month-08");
+            case 3:
+                return new DateTime("$year-$month-15");
+            case 4:
+                return new DateTime("$year-$month-22");
+            case 5:
+                return new DateTime("$year-$month-29");
+            default:
+                return new DateTime("$year-$month-01");
+        }
+    }
+
+    /**
+     * Вычисление общей суммы фонда оплаты труда (ФОТ) - зарплаты и отпускных для магазина за указанный период.
+     *
+     * Этот метод сначала получает записи о сменах сотрудников за указанный период и сумму отпускных.
+     * Затем для каждой записи определяется соответствующая дневная зарплата, и она добавляется к общей сумме.
+     * Если в записи уже указана зарплата за смену, она используется вместо дневной зарплаты.
+     * В конце к общей сумме зарплат добавляется сумма отпускных.
+     *
+     * @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);
+
+        $totalSalary = 0.0;
+        foreach ($records as $record) {
+            // Находим самую позднюю запись по admin_id
+            $payment = EmployeePayment::find()
+                ->where(['admin_id' => $record->admin_id])
+                ->orderBy(['date' => SORT_DESC])
+                ->one();
+            $dailyPayment = $payment ? $payment->daily_payment : 0.0;
+
+            if (!empty($record->salary_shift)) {
+                $totalSalary += $record->salary_shift;
+            } else {
+                $totalSalary += $dailyPayment;
+            }
+        }
+
+        return $totalSalary + $vacationSum;
+    }
+
     public static function calculateMonthSales($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'));
@@ -564,6 +875,8 @@ class MotivationService
         foreach ($storeIds as $store_id) {
             if (isset($motivations[$store_id])) {
                 if (isset($salesOffline[$store_id])) {
+                    $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemOffline->code);
+
                     $motivationValueOffline = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id,
                         'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemOffline->code])->one();
                     if (!$motivationValueOffline) {
@@ -573,13 +886,15 @@ class MotivationService
                         $motivationValueOffline->value_id = $motivationCostsItemOffline->code;
                         $motivationValueOffline->value_type = $motivationCostsItemOffline->data_type;
                     }
-                    $motivationValueOffline->value_float = $salesOffline[$store_id]['total'];
+                    $motivationValueOffline->value_float = $salesOffline[$store_id]['total'] + $correction;
                     $motivationValueOffline->save();
                     if ($motivationValueOffline->getErrors()) {
                         throw new \Exception(Json::encode($motivationValueOffline->getErrors()));
                     }
                 }
                 if (isset($salesOnline[$store_id])) {
+                    $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemOnline->code);
+
                     $motivationValueOnline = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id,
                         'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemOnline->code])->one();
                     if (!$motivationValueOnline) {
@@ -589,7 +904,7 @@ class MotivationService
                         $motivationValueOnline->value_id = $motivationCostsItemOnline->code;
                         $motivationValueOnline->value_type = $motivationCostsItemOnline->data_type;
                     }
-                    $motivationValueOnline->value_float = $salesOnline[$store_id]['total'];
+                    $motivationValueOnline->value_float = $salesOnline[$store_id]['total'] + $correction;
                     $motivationValueOnline->save();
                     if ($motivationValueOnline->getErrors()) {
                         throw new \Exception(Json::encode($motivationValueOnline->getErrors()));
@@ -687,6 +1002,8 @@ class MotivationService
         foreach ($storeIds as $store_id) {
             if (isset($motivations[$store_id])) {
                 if (isset($salesProduct[$store_id])) {
+                    $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemServices->code);
+
                     $motivationValueService = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id,
                         'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemServices->code])->one();
                     if (!$motivationValueService) {
@@ -696,13 +1013,15 @@ class MotivationService
                         $motivationValueService->value_id = $motivationCostsItemServices->code;
                         $motivationValueService->value_type = $motivationCostsItemServices->data_type;
                     }
-                    $motivationValueService->value_float = $salesProduct[$store_id]['total'];
+                    $motivationValueService->value_float = $salesProduct[$store_id]['total'] + $correction;
                     $motivationValueService->save();
                     if ($motivationValueService->getErrors()) {
                         throw new \Exception(Json::encode($motivationValueService->getErrors()));
                     }
                 }
                 if (isset($salesProductDelivery[$store_id])) {
+                    $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemServicesDelivery->code);
+
                     $motivationValueServiceDelivery = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id,
                         'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemServicesDelivery->code])->one();
                     if (!$motivationValueServiceDelivery) {
@@ -712,7 +1031,7 @@ class MotivationService
                         $motivationValueServiceDelivery->value_id = $motivationCostsItemServicesDelivery->code;
                         $motivationValueServiceDelivery->value_type = $motivationCostsItemServicesDelivery->data_type;
                     }
-                    $motivationValueServiceDelivery->value_float = $salesProductDelivery[$store_id]['total'];
+                    $motivationValueServiceDelivery->value_float = $salesProductDelivery[$store_id]['total'] + $correction;
                     $motivationValueServiceDelivery->save();
                     if ($motivationValueServiceDelivery->getErrors()) {
                         throw new \Exception(Json::encode($motivationValueServiceDelivery->getErrors()));
@@ -747,6 +1066,9 @@ class MotivationService
                     }
                     $motivationCostsItem = MotivationCostsItem::find()->where(['name' => $motivationItemType])->one();
                     /** @var $motivationCostsItem MotivationCostsItem */
+
+                    $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItem->code);
+
                     $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id,
                         'motivation_group_id' => $motivationValueGroup->id, 'value_id' => $motivationCostsItem->code])->one();
                     if (!$motivationValue) {
@@ -756,7 +1078,7 @@ class MotivationService
                         $motivationValue->value_id = $motivationCostsItem->code;
                         $motivationValue->value_type = $motivationCostsItem->data_type;
                     }
-                    $motivationValue->value_float = $data['total'];
+                    $motivationValue->value_float = $data['total'] + $correction;
                     $motivationValue->save();
                     if ($motivationValue->getErrors()) {
                         throw new \Exception(Json::encode($motivationValue->getErrors()));
@@ -765,4 +1087,23 @@ class MotivationService
             }
         }
     }
+
+    public static function calculateMonthSalary($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'));
+
+        $motivations = Motivation::find()
+            ->where(['year' => $year, 'month' => $month])
+            ->all();
+
+        foreach ($motivations as $motivation) {
+            $store_id = $motivation->store_id;
+            $monthlyTotalSalary = self::calculateTotalSalary(date("Y-m-d", strtotime($monthStart)), date("Y-m-d", strtotime($monthEnd)), $store_id);
+
+            $correction = self::getMotivationValue($motivation->id, 8, 11);
+
+            self::saveOrUpdateMotivationValue($motivation->id,
+                6, 11, "float", $monthlyTotalSalary + $correction);
+        }
+    }
 }