From 51f31a7a8236b6d27ccba04db77502693a8a85c7 Mon Sep 17 00:00:00 2001 From: Marina Zozirova Date: Thu, 5 Sep 2024 10:22:14 +0000 Subject: [PATCH] =?utf8?q?ERP-106=20=D0=A1=D0=B1=D0=BE=D1=80=20=D0=B4?= =?utf8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=20=D1=80=D0=B0?= =?utf8?q?=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D1=8B=D0=BC=20=D0=BC=D0=B0=D1=82?= =?utf8?q?=D0=B5=D1=80=D0=B8=D0=B0=D0=BB=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/services/MotivationService.php | 300 +++++++++++++++++++-------- 1 file changed, 216 insertions(+), 84 deletions(-) diff --git a/erp24/services/MotivationService.php b/erp24/services/MotivationService.php index 1e5f66a5..1148c596 100644 --- a/erp24/services/MotivationService.php +++ b/erp24/services/MotivationService.php @@ -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; + + } + } -- 2.39.5