]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
ERP-106 Сбор данных по расходным материалам
authorMarina Zozirova <marina.zozirova@erp-flowers.ru>
Thu, 5 Sep 2024 10:22:14 +0000 (10:22 +0000)
committerAleksey Filippov <aleksey.filippov@erp-flowers.ru>
Thu, 5 Sep 2024 10:22:14 +0000 (10:22 +0000)
erp24/services/MotivationService.php

index 1e5f66a5af826d9bdf99a1068949500a82e60d3f..1148c59638d8c09537d7a533a3a0e8481b988377 100644 (file)
@@ -22,6 +22,8 @@ use yii_app\records\Sales;
 use yii_app\records\SalesProducts;
 use yii_app\records\TimetableFactModel;
 use yii_app\records\Timetable;
+use yii_app\records\WriteOffsProducts;
+
 
 class MotivationService
 {
@@ -93,35 +95,36 @@ class MotivationService
 
     // 6. Создание массива дополнительных элементов
     static $additionalItems = [
-        80 => ['name' => 'Выручка от реализации',                 'code' => self::CODE_REVENUE_FROM_SALES],
-        90 => ['name' => 'Продажа товара',                        'code' => self::CODE_SALE_OF_GOODS],
-        115 => ['name' => 'Прочие услуги',                        'code' => self::CODE_OTHER_SERVICES],
-        135 => ['name' => 'Прямые расходы на продажу',            'code' => self::CODE_DIRECT_SELLING_COSTS],
-        139 => ['name' => 'Себестоимость товара',                 'code' => self::CODE_COST_PRICE_OF_GOODS],
+        80 => ['name' => 'Выручка от реализации', 'code' => self::CODE_REVENUE_FROM_SALES],
+        90 => ['name' => 'Продажа товара', 'code' => self::CODE_SALE_OF_GOODS],
+        115 => ['name' => 'Прочие услуги', 'code' => self::CODE_OTHER_SERVICES],
+        135 => ['name' => 'Прямые расходы на продажу', 'code' => self::CODE_DIRECT_SELLING_COSTS],
+        139 => ['name' => 'Себестоимость товара', 'code' => self::CODE_COST_PRICE_OF_GOODS],
         143 => ['name' => 'Услуги агентов (Расходы на закупку, хранение, доставку товара)', 'code' => self::CODE_AGENT_SERVICES_EXPENSES_FOR_PURCHASING_STORING_DELIVERING_GOODS],
-        146 => ['name' => 'Брак, пересорт',                       'code' => self::CODE_DEFECT_RESORTING],
-        192 => ['name' => 'Маржинальный доход',                   'code' => self::CODE_MARGINAL_INCOME],
+        146 => ['name' => 'Брак, пересорт', 'code' => self::CODE_DEFECT_RESORTING],
+        192 => ['name' => 'Маржинальный доход', 'code' => self::CODE_MARGINAL_INCOME],
         194 => ['name' => 'Операционные расходы (Себестоимость)', 'code' => self::CODE_OPERATIONAL_EXPANSES_COST],
-        196 => ['name' => 'Оплата труда',                         'code' => self::CODE_PAYMENT],
-        205 => ['name' => 'Содержание помещения',                 'code' => self::CODE_MAINTENANCE_OF_PRIMISES],
-        245 => ['name' => 'Расходы по доставке',                  'code' => self::CODE_DELIVERY_COST],
-        275 => ['name' => 'Содержание и обслуживание ОС и НМА',   'code' => self::CODE_MAINTENANCE_AND_SERVICE_OF_FIXED_ASSETS_AND_INTANGIBLE_ASSETS],
-        315 => ['name' => 'Услуги связи',                         'code' => self::CODE_COMMUNICATION_SERVICES],
-        325 => ['name' => 'Прочие операционные расходы',          'code' => self::CODE_OTHER_OPERATING_EXPENSES],
-        353 => ['name' => 'Валовая прибыль',                      'code' => self::CODE_GROSS_PROFIT],
-        355 => ['name' => 'Общехозяйственные расходы',            'code' => self::CODE_GENERAL_BUSINESS_EXPENSES],
-        357 => ['name' => 'Бухгалтерия и финансы',                'code' => self::CODE_ACCOUNTING_AND_FINANCE],
-        365 => ['name' => 'Юридическое сопровождение',            'code' => self::CODE_LEGAL_SUPPORT],
-        375 => ['name' => 'HR- услуги',                           'code' => self::CODE_HR_SERVICES],
-        395 => ['name' => 'IT услуги',                            'code' => self::CODE_IT_SERVICES],
-        425 => ['name' => 'Чистая прибыль',                       'code' => self::CODE_NET_PROFIT],
-        427 => ['name' => 'Рентабельность по чистой прибыли, %',  'code' => self::CODE_NET_PROFIT_MARGIN_PERCENT],
+        196 => ['name' => 'Оплата труда', 'code' => self::CODE_PAYMENT],
+        205 => ['name' => 'Содержание помещения', 'code' => self::CODE_MAINTENANCE_OF_PRIMISES],
+        245 => ['name' => 'Расходы по доставке', 'code' => self::CODE_DELIVERY_COST],
+        275 => ['name' => 'Содержание и обслуживание ОС и НМА', 'code' => self::CODE_MAINTENANCE_AND_SERVICE_OF_FIXED_ASSETS_AND_INTANGIBLE_ASSETS],
+        315 => ['name' => 'Услуги связи', 'code' => self::CODE_COMMUNICATION_SERVICES],
+        325 => ['name' => 'Прочие операционные расходы', 'code' => self::CODE_OTHER_OPERATING_EXPENSES],
+        353 => ['name' => 'Валовая прибыль', 'code' => self::CODE_GROSS_PROFIT],
+        355 => ['name' => 'Общехозяйственные расходы', 'code' => self::CODE_GENERAL_BUSINESS_EXPENSES],
+        357 => ['name' => 'Бухгалтерия и финансы', 'code' => self::CODE_ACCOUNTING_AND_FINANCE],
+        365 => ['name' => 'Юридическое сопровождение', 'code' => self::CODE_LEGAL_SUPPORT],
+        375 => ['name' => 'HR- услуги', 'code' => self::CODE_HR_SERVICES],
+        395 => ['name' => 'IT услуги', 'code' => self::CODE_IT_SERVICES],
+        425 => ['name' => 'Чистая прибыль', 'code' => self::CODE_NET_PROFIT],
+        427 => ['name' => 'Рентабельность по чистой прибыли, %', 'code' => self::CODE_NET_PROFIT_MARGIN_PERCENT],
         428 => ['name' => 'Минимальный порог Чистой прибыли, руб.', 'code' => self::CODE_NET_PROFIT_THRESHOLD_RUB],
-        435 => ['name' => 'Расчет премии',                        'code' => self::CODE_CALCULATION_OF_PREMIUM]
+        435 => ['name' => 'Расчет премии', 'code' => self::CODE_CALCULATION_OF_PREMIUM]
     ];
 
 // Код из таблицы MotivationCostsItem строки - Фонд оплаты труда персонала
     const MCI_FOT = 11;
+
     /**
      * Получает ассоциативный массив алиасов групп мотивации.
      *
@@ -191,6 +194,8 @@ class MotivationService
         $result = [];
         $groupAliases = self::getMotivationValueGroupAliases();
 
+        $consumable = self::getConsumable($motivation->id);
+
         foreach ($motivationValues as $value) {
             $valueId = $value->value_id;
             $groupId = $value->motivation_group_id;
@@ -208,11 +213,11 @@ class MotivationService
                     'name' => $costsItem->name,
                     'plan' => null,
                     'adjustment' => null,
-                    'week1' => null,
-                    'week2' => null,
-                    'week3' => null,
-                    'week4' => null,
-                    'week5' => null,
+                    'week1' => $value->value_id == self::CODE_CONSUMABLES_SALES_SUPPORT ? $consumable['week1'] : null,
+                    'week2' => $value->value_id == self::CODE_CONSUMABLES_SALES_SUPPORT  ? $consumable['week2'] : null,
+                    'week3' => $value->value_id == self::CODE_CONSUMABLES_SALES_SUPPORT  ? $consumable['week3'] : null,
+                    'week4' => $value->value_id == self::CODE_CONSUMABLES_SALES_SUPPORT  ? $consumable['week4'] : null,
+                    'week5' => $value->value_id == self::CODE_CONSUMABLES_SALES_SUPPORT  ? $consumable['week5'] : null,
                     'forecast' => null,
                     'fact' => null,
                     'deviation' => null,
@@ -232,7 +237,9 @@ class MotivationService
                     break;
             }
 
-            $result[$costsItem->order][$groupAlias] = $actualValue;
+            if ($value->value_id != self::CODE_CONSUMABLES_SALES_SUPPORT) {
+                $result[$costsItem->order][$groupAlias] = $actualValue;
+            }
         }
 
         //  Сортировка результата по ключу (order)
@@ -335,31 +342,34 @@ class MotivationService
                         $error = "Некорректно название элемента '" . ($row[1] ?? '') . "' Ожидается: '" . $motivationCostsItems[$row[0]]->name . "' [$ind,1]";
                         break;
                     }
-                    if (trim($row[2])  == '') {
+                    if (trim($row[2]) == '') {
                         $rows[] = $row;
                         continue;
                     }
                     switch ($motivationCostsItems[$row[0]]->data_type) {
-                        case MotivationCostsItem::DATA_TYPE_INT: {
-                                if (is_int($row[2])) {
-                                    $value = (int)$row[2];
-                                } else {
-                                    $error = "Не целое число [$ind,2] '" . $row[2] . "'";
-                                };
-                                break;
-                            }
-                        case MotivationCostsItem::DATA_TYPE_FLOAT: {
-                                if (is_int($row[2]) || is_float($row[2])) {
-                                    $value = (float)$row[2];
-                                } else {
-                                    $error = "Не дробь [$ind,2] '" . $row[2] . "'";
-                                }
-                                break;
-                            }
-                        case MotivationCostsItem::DATA_TYPE_STRING: {
-                                $value = $row[2];
-                                break;
+                        case MotivationCostsItem::DATA_TYPE_INT:
+                        {
+                            if (is_int($row[2])) {
+                                $value = (int)$row[2];
+                            } else {
+                                $error = "Не целое число [$ind,2] '" . $row[2] . "'";
+                            };
+                            break;
+                        }
+                        case MotivationCostsItem::DATA_TYPE_FLOAT:
+                        {
+                            if (is_int($row[2]) || is_float($row[2])) {
+                                $value = (float)$row[2];
+                            } else {
+                                $error = "Не дробь [$ind,2] '" . $row[2] . "'";
                             }
+                            break;
+                        }
+                        case MotivationCostsItem::DATA_TYPE_STRING:
+                        {
+                            $value = $row[2];
+                            break;
+                        }
                     }
                     if (!empty($error)) {
                         break;
@@ -408,18 +418,21 @@ class MotivationService
                     }
                     $motivationValue->value_type = $motivationCostsItems[$row[0]]->data_type;
                     switch ($motivationValue->value_type) {
-                        case MotivationCostsItem::DATA_TYPE_INT: {
-                                $motivationValue->value_int = (int)$row[2];
-                                break;
-                            }
-                        case MotivationCostsItem::DATA_TYPE_FLOAT: {
-                                $motivationValue->value_float = (float)$row[2];
-                                break;
-                            }
-                        case MotivationCostsItem::DATA_TYPE_STRING: {
-                                $motivationValue->value_string = '' . $row[2];
-                                break;
-                            }
+                        case MotivationCostsItem::DATA_TYPE_INT:
+                        {
+                            $motivationValue->value_int = (int)$row[2];
+                            break;
+                        }
+                        case MotivationCostsItem::DATA_TYPE_FLOAT:
+                        {
+                            $motivationValue->value_float = (float)$row[2];
+                            break;
+                        }
+                        case MotivationCostsItem::DATA_TYPE_STRING:
+                        {
+                            $motivationValue->value_string = '' . $row[2];
+                            break;
+                        }
                     }
                     $motivationValue->save();
                     if ($motivationValue->getErrors()) {
@@ -436,7 +449,8 @@ class MotivationService
         return compact('errors');
     }
 
-    public static function calculateDefectCost($store_id, $year, $month) {
+    public static function calculateDefectCost($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'));
 
@@ -473,7 +487,7 @@ class MotivationService
 
                 foreach (MotivationCostsItem::getWriteOffsItems() as $key) {
                     $sum = 0;
-                    foreach($writeOffs as $data) {
+                    foreach ($writeOffs as $data) {
                         if (($data['type'] ?? '') != $key) {
                             continue;
                         }
@@ -500,7 +514,8 @@ class MotivationService
         }
     }
 
-    public static function calculateServiceAssemblyAndDeliveryCost($store_id, $year, $month) {
+    public static function calculateServiceAssemblyAndDeliveryCost($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'));
 
@@ -595,7 +610,8 @@ class MotivationService
         }
     }
 
-    public static function calculateSales($store_id, $year, $month) {
+    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'));
 
@@ -663,7 +679,8 @@ class MotivationService
         }
     }
 
-    public static function calculateMonthForecast($store_id, $year, $month) {
+    public static function calculateMonthForecast($store_id, $year, $month)
+    {
         $motivationCostsItem = MotivationCostsItem::find()->all();
         $motivationCostsItemCodes = ArrayHelper::getColumn($motivationCostsItem, 'code');
         $additionalItemsCodes = ArrayHelper::getColumn(self::$additionalItems, 'code');
@@ -672,7 +689,7 @@ class MotivationService
 
         $motivationValueGroups = [];
         foreach (range(1, 5) as $ind) {
-            $motivationValueGroups []= MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one();
+            $motivationValueGroups [] = MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one();
         }
         /** @var $motivationValueGroups MotivationValueGroup[] */
 
@@ -691,22 +708,36 @@ class MotivationService
                 $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();
+                        '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 невозможен для вычисления прогноза")); }
+                            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 невозможен для вычисления прогноза"));
+                            }
                         };
                     }
                 }
                 self::saveOrUpdateMotivationValue($motivation->id, "forecast", $code, $sum_type, $sum);
+
             }
         }
     }
 
-    public static function calculatePersonalCount($store_id, $year, $month) {
+    public static function calculatePersonalCount($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'));
 
@@ -771,11 +802,10 @@ class MotivationService
      * @return float Возвращает общую сумму отпускных за указанный период.
      *               Если записей не найдено, возвращается 0.0.
      */
-    public static function getVacationsSum($startDate, $endDate, $storeId):float
+    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])
@@ -828,7 +858,7 @@ class MotivationService
             ->all();
 
         // Получаем массив значений value_float
-        $values = array_map(function($record) {
+        $values = array_map(function ($record) {
             return $record->value_float;
         }, $records);
 
@@ -992,7 +1022,7 @@ class MotivationService
      * @return float Возвращает общую сумму фонда оплаты труда за указанный период,
      *               включая зарплаты и отпускные.
      */
-    public static function calculateTotalSalary($startDate, $endDate, $storeId):float
+    public static function calculateTotalSalary($startDate, $endDate, $storeId): float
     {
         $records = self::getTimetableFactRecordsByDateAndStore($startDate, $endDate, $storeId);
         $vacationSum = self::getVacationsSum($startDate, $endDate, $storeId);
@@ -1037,7 +1067,7 @@ class MotivationService
     /**
      * Возвращает массив, представляющий недели месяца (с указаниме начала и конца недели) для указанного года и месяца.
      *
-     * @param int|string $year  Год в виде числа или строки (например, `2024` или `'2024'`).
+     * @param int|string $year Год в виде числа или строки (например, `2024` или `'2024'`).
      * @param int|string $month Месяц в виде числа или строки (например, `2` или `'02'`).
      *
      * @return array Массив недель месяца, где каждая неделя представлена ассоциативным массивом с ключами 'start' и 'end'.
@@ -1498,7 +1528,8 @@ class MotivationService
         }
     }
 
-    public static function calculateFactFormula($motivationDataTableSort, $year, $month) {
+    public static function calculateFactFormula($motivationDataTableSort, $year, $month)
+    {
         // Определяем последний день месяца
         $lastDayOfMonth = date('t', strtotime("$year-$month-01"));
 
@@ -1516,12 +1547,28 @@ class MotivationService
             }
             $indMap[intval($row['code'])] = $ind;
         }
-        foreach (range(0,7) as $indexItem) {
+        foreach (range(0, 7) as $indexItem) {
             switch ($indexItem) {
-                case 0: { $column = 'plan'; break; }
-                case 6: { $column = 'fact'; break; }
-                case 7: { $column = 'forecast'; break; }
-                default: { $column = 'week' . $indexItem; break; }
+                case 0:
+                {
+                    $column = 'plan';
+                    break;
+                }
+                case 6:
+                {
+                    $column = 'fact';
+                    break;
+                }
+                case 7:
+                {
+                    $column = 'forecast';
+                    break;
+                }
+                default:
+                {
+                    $column = 'week' . $indexItem;
+                    break;
+                }
             }
 
             // Проверяем и инициализируем каждую запись, если она отсутствует
@@ -1689,7 +1736,7 @@ class MotivationService
         }
 
         // Отклонение
-        $deviationFunc = function ($code) use(&$motivationDataTableSort, &$indMap) {
+        $deviationFunc = function ($code) use (&$motivationDataTableSort, &$indMap) {
             if ($motivationDataTableSort[$indMap[$code]]["plan"] != 0) {
                 $motivationDataTableSort[$indMap[$code]]["deviation"] =
                     $motivationDataTableSort[$indMap[$code]]["fact"] /
@@ -1890,7 +1937,7 @@ class MotivationService
      * @param int $storeId ID магазина.
      * @return bool Возвращает true в случае успешного сохранения данных, иначе false.
      */
-    public static function saveCostMotivation($storeId, $year, $month )
+    public static function saveCostMotivation($storeId, $year, $month)
     {
         // Получаем идентификатор мотивации
         $motivation = Motivation::find()
@@ -1959,4 +2006,89 @@ class MotivationService
         return $success;
     }
 
+    public static function getConsumable($motivationId)
+    {
+        $summa = [];
+        $motivation = Motivation::findOne($motivationId);
+        $startDate = "$motivation->year-$motivation->month-01 00:00:00";
+        $endDate = "$motivation->year-$motivation->month-" . date('t', strtotime($startDate)) . " 23:59:59";
+
+        $stores = ArrayHelper::map(CityStore::find()
+            ->joinWith('storeGuid')
+            ->select('city_store.id as id, export_import_table.export_val as store_guid')
+            ->andWhere('city_store.id is not null')
+            ->andWhere('export_import_table.export_val is not null')
+            ->andWhere('export_import_table.export_val <> \'\'')
+            ->orderBy('id desc')
+            ->asArray()
+            ->all(), 'id', 'store_guid');
+
+        if (!empty(array_key_exists($motivation->store_id,$stores))) {
+            $writeOffs = WriteOffs::find()
+                ->andWhere(['type' => 'Расходные материалы (обеспечение продаж)'])
+                ->andWhere(['>', 'date', $startDate])
+                ->andWhere(['<=', 'date', $endDate])
+                ->andWhere(['store_id' => $stores[$motivation->store_id]])
+                ->all();
+
+            foreach ($writeOffs as $writeOff) {
+                $writeOffProducts = WriteOffsProducts::find()
+                    ->andWhere(['write_offs_id' => $writeOff->id])
+                    ->all();
+
+                foreach ($writeOffProducts as $offProduct) {
+                    if (empty(array_search($writeOff->store_id, $stores))) {
+                        continue;
+                    }
+
+                    $price = SelfCostProduct::find()
+                        ->andWhere(['product_guid' => $offProduct->product_id])
+                        ->andWhere(['store_id' => array_search($writeOff->store_id, $stores)])
+                        ->andWhere(['date' => date('Y-m-d', strtotime($writeOff->date))])
+                        ->one();
+                    if (empty($price)) {
+                        $price = $offProduct;
+                    }
+
+
+                    $sum = $price->price * $offProduct->quantity;
+
+                    $summa[] = ['date' => date('Y-m-d', strtotime($writeOff->date)), 'sum' => $sum];
+
+                }
+            }
+        }
+
+        $weeklySums = array_fill(1, 5, 0);
+        $weeklySums = array_combine(
+            array_map(function($i) { return "week$i"; }, array_keys($weeklySums)),
+            $weeklySums
+        );
+
+        foreach ($summa as $value) {
+            $week = Motivation::getWeek($value['date']);
+            if ($week >= 1 && $week <= 5) {
+                $key = "week$week";
+                $weeklySums[$key] += $value['sum'];
+            }
+        }
+
+        $motivationValues = MotivationValue::find()
+            ->andWhere(['motivation_id' => $motivationId])
+            ->andWhere(['value_id' => self::CODE_CONSUMABLES_SALES_SUPPORT])
+            ->select('motivation_group_id, value_float')
+            ->all();
+
+        foreach ($motivationValues as $value) {
+            $week = $value['motivation_group_id'];
+            if ($week >= 1 && $week <= 5) {
+                $key = "week$week";
+                $weeklySums[$key] += $value['value_float'];
+            }
+        }
+
+        return $weeklySums;
+
+    }
+
 }