From: Vladimir Fomichev Date: Thu, 29 Aug 2024 14:29:29 +0000 (+0300) Subject: Merge branch 'develop' into feature_fomichev_erp_104_agents_services_computation X-Git-Tag: 1.4~15^2~11 X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=32208978d3a7e9f83cb96d3a4bfe74b04928d766;p=erp24_rep%2Fyii-erp24%2F.git Merge branch 'develop' into feature_fomichev_erp_104_agents_services_computation # Conflicts: # erp24/services/MotivationService.php --- 32208978d3a7e9f83cb96d3a4bfe74b04928d766 diff --cc erp24/services/MotivationService.php index e34eb6bb,dad9f984..15c21924 --- a/erp24/services/MotivationService.php +++ b/erp24/services/MotivationService.php @@@ -1105,106 -1106,243 +1105,346 @@@ class MotivationServic return $weeks; } + public static function calculateFactFormula($motivationDataTableSort, $year, $month) { + // Определяем последний день месяца + $lastDayOfMonth = date('t', strtotime("$year-$month-01")); + + $indMap = []; + foreach ($motivationDataTableSort as $ind => $row) { + if (!key_exists('code', $row)) { + continue; + } + $indMap[intval($row['code'])] = $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[self::CODE_SALE_OF_GOODS]][$column] = // "Продажа товара" + $motivationDataTableSort[$indMap[self::CODE_OFFLINE_SALES]][$column] + // "Оффлайн продажи" + $motivationDataTableSort[$indMap[self::CODE_ONLINE_SALES]][$column] + 0; // "Онлайн продажи" + + $motivationDataTableSort[$indMap[self::CODE_OTHER_SERVICES]][$column] = // "Прочие услуги" + $motivationDataTableSort[$indMap[self::CODE_ASSEMBLY_SERVICES]][$column] + // "Услуги по сборке" + $motivationDataTableSort[$indMap[self::CODE_DELIVERY_SERVICES]][$column] + 0; // "Услуги по доставке" + + $motivationDataTableSort[$indMap[self::CODE_REVENUE_FROM_SALES]][$column] = // "Выручка от реализации" + $motivationDataTableSort[$indMap[self::CODE_SALE_OF_GOODS]][$column] + // "Продажа товара" + $motivationDataTableSort[$indMap[self::CODE_OTHER_SERVICES]][$column] + 0; // "Прочие услуги" + + $motivationDataTableSort[$indMap[self::CODE_COST_PRICE_OF_GOODS]][$column] = // "Себестоимость товара" + $motivationDataTableSort[$indMap[self::CODE_COSTS_OF_GOODS]][$column] + 0; // "Стоимость товара" + + $motivationDataTableSort[$indMap[self::CODE_AGENT_SERVICES_EXPENSES_FOR_PURCHASING_STORING_DELIVERING_GOODS]][$column] = // "Услуги агентов (Расходы на закупку, хранение, доставку товара)" + ( + $motivationDataTableSort[$indMap[self::CODE_COST_PRICE_OF_GOODS]][$column] + // "Себестоимость товара" + $motivationDataTableSort[$indMap[self::CODE_DEFECT_RESORTING]][$column] // "Брак, пересорт" + ) * + $motivationDataTableSort[$indMap[self::CODE_AGENT_SERVICES_TARIFF]][$column]; // "Услуги агентов (тариф)" + + $motivationDataTableSort[$indMap[self::CODE_DEFECT_RESORTING]][$column] = // "Брак, пересорт" + $motivationDataTableSort[$indMap[self::CODE_DELIVERY_DEFECTS]][$column] + // "Брак с поставки" + $motivationDataTableSort[$indMap[self::CODE_WRITE_OFF_ILLIQUID_GOODS_SPOOLAGE_EXPIRATION_OF_SHELF_LIFE]][$column] + // "Списание неликвидного товара: порча, истечение срока годности" + $motivationDataTableSort[$indMap[self::CODE_EQUIPMENT_FAILURE_DEFECT]][$column] + // "Брак из-за поломки оборудования" + $motivationDataTableSort[$indMap[self::CODE_REGRADING]][$column] + 0; // "Пересорт" + + $motivationDataTableSort[$indMap[self::CODE_DIRECT_SELLING_COSTS]][$column] = // "Прямые расходы на продажу" + $motivationDataTableSort[$indMap[self::CODE_COST_PRICE_OF_GOODS]][$column] + // "Себестоимость товара" + $motivationDataTableSort[$indMap[self::CODE_AGENT_SERVICES_EXPENSES_FOR_PURCHASING_STORING_DELIVERING_GOODS]][$column] + // "Услуги агентов (Расходы на закупку, хранение, доставку товара)" + $motivationDataTableSort[$indMap[self::CODE_DEFECT_RESORTING]][$column] + // "Брак, пересорт" + $motivationDataTableSort[$indMap[self::CODE_CONSUMABLES_SALES_SUPPORT]][$column] + 0; // "Расходные материалы (обеспечение продаж)" + + $motivationDataTableSort[$indMap[self::CODE_MARGINAL_INCOME]][$column] = // "Маржинальный доход" + $motivationDataTableSort[$indMap[self::CODE_REVENUE_FROM_SALES]][$column] - // "Выручка от реализации" + $motivationDataTableSort[$indMap[self::CODE_DIRECT_SELLING_COSTS]][$column] + 0; // "Прямые расходы на продажу" + + $motivationDataTableSort[$indMap[self::CODE_PAYMENT]][$column] = // "Оплата труда" + $motivationDataTableSort[$indMap[self::CODE_PAYROLL_FUND]][$column] + 0; // "Фонд оплаты труда персонала" + + $motivationDataTableSort[$indMap[self::CODE_MAINTENANCE_OF_PRIMISES]][$column] = // "Содержание помещения" + $motivationDataTableSort[$indMap[self::CODE_RENT]][$column] + // "Аренда" + $motivationDataTableSort[$indMap[self::CODE_PUBLIC_SERVICES]][$column] + // "Коммунальные услуги" + $motivationDataTableSort[$indMap[self::CODE_SECURITY]][$column] + // "Охрана" + $motivationDataTableSort[$indMap[self::CODE_CLEANING_SERVICES_FOR_PREMISES_AND_TERRITORY]][$column] + 0; // "Услуги по уборке помещений и территории" + + $motivationDataTableSort[$indMap[self::CODE_DELIVERY_COST]][$column] = // "Расходы по доставке" + $motivationDataTableSort[$indMap[self::CODE_DELIVERY_TO_CLIENT_CURRIER]][$column] + // "Доставка до клиента курьер" + $motivationDataTableSort[$indMap[self::CODE_DELIVERY_TO_CLIENT_TAXI]][$column] + 0; // "Доставка до клиента такси" + + $motivationDataTableSort[$indMap[self::CODE_MAINTENANCE_AND_SERVICE_OF_FIXED_ASSETS_AND_INTANGIBLE_ASSETS]][$column] = // "Содержание и обслуживание ОС и НМА" + $motivationDataTableSort[$indMap[self::CODE_REFRIGERATION_EQUIPMENT_REPAIR_MAINTANANCE]][$column] + // "Холодильное оборудование (ремонт, содержание, ТО)" + $motivationDataTableSort[$indMap[self::CODE_COSTS_FOR_MAINTENANCE_AND_REPAIR_OF_OFFICE_EQUIPMENT_INCLUDING_CONSUMABLES]][$column] + // "Расходы на содержание и ремонт оргтехники, в т.ч. расходные материалы" + $motivationDataTableSort[$indMap[self::CODE_EXPENSES_FOR_MAINENANCE_AND_REPAIR_OF_OTHER_FIXED_ASSETS]][$column] + // "Расходы на содержание и ремонт прочих ОС" + $motivationDataTableSort[$indMap[self::CODE_MAINTENANCE_OF_CASH_REGISTERS]][$column] + 0; // "Техническое обслуживание кассовых аппаратов" + + $motivationDataTableSort[$indMap[self::CODE_COMMUNICATION_SERVICES]][$column] = // "Услуги связи" + $motivationDataTableSort[$indMap[self::CODE_INTERNET]][$column] + 0; // "Интернет" + + $motivationDataTableSort[$indMap[self::CODE_OTHER_OPERATING_EXPENSES]][$column] = // "Прочие операционные расходы" + $motivationDataTableSort[$indMap[self::CODE_HOUSEHOLD_GOODS]][$column] + // "Хозяйственные товары" + $motivationDataTableSort[$indMap[self::CODE_STATIONARY]][$column] + // "Канцтовары" + $motivationDataTableSort[$indMap[self::CODE_DRINKING_WATER]][$column] + 0; // "Вода питьевая" + + $motivationDataTableSort[$indMap[self::CODE_OPERATIONAL_EXPANSES_COST]][$column] = // "Операционные расходы (Себестоимость)" + $motivationDataTableSort[$indMap[self::CODE_PAYMENT]][$column] + // "Оплата труда" + $motivationDataTableSort[$indMap[self::CODE_MAINTENANCE_OF_PRIMISES]][$column] + // "Содержание помещения" + $motivationDataTableSort[$indMap[self::CODE_DELIVERY_COST]][$column] + // "Расходы по доставке" + $motivationDataTableSort[$indMap[self::CODE_MARKETPLACE_SERVICES]][$column] + // "Услуги маркетплейсов" + $motivationDataTableSort[$indMap[self::CODE_MAINTENANCE_AND_SERVICE_OF_FIXED_ASSETS_AND_INTANGIBLE_ASSETS]][$column] + // "Содержание и обслуживание ОС и НМА" + $motivationDataTableSort[$indMap[self::CODE_COMMUNICATION_SERVICES]][$column] + // "Услуги связи" + $motivationDataTableSort[$indMap[self::CODE_OTHER_OPERATING_EXPENSES]][$column] + 0; // "Прочие операционные расходы" + + $motivationDataTableSort[$indMap[self::CODE_GROSS_PROFIT]][$column] = // "Валовая прибыль" + $motivationDataTableSort[$indMap[self::CODE_MARGINAL_INCOME]][$column] - // "Маржинальный доход" + $motivationDataTableSort[$indMap[self::CODE_OPERATIONAL_EXPANSES_COST]][$column] + 0; // "Операционные расходы (Себестоимость)" + + $motivationDataTableSort[$indMap[self::CODE_ACCOUNTING_AND_FINANCE]][$column] = // "Бухгалтерия и финансы" + $motivationDataTableSort[$indMap[self::CODE_ACCOUNTING_SERVICES_SETTING_UP_AND_MAINTAINING_ACCOUNTING_AND_TAX_RECORDS]][$column] + 0; // "Бухгалтерские услуги: постановка и ведение БУ и НУ" + + $motivationDataTableSort[$indMap[self::CODE_LEGAL_SUPPORT]][$column] = // "Юридическое сопровождение" + $motivationDataTableSort[$indMap[self::CODE_LEGAL_SERVICES]][$column] + 0; // "Юридические услуги" + + $motivationDataTableSort[$indMap[self::CODE_HR_SERVICES]][$column] = // "HR- услуги" + $motivationDataTableSort[$indMap[self::CODE_PERSONAL_ADMINISTRATION_LABOR_PROTECTION]][$column] + // "Кадровое администрирование, охрана труда" + $motivationDataTableSort[$indMap[self::CODE_RECRUITMENT_SERVICES]][$column] + 0; // "Услуги по подбору персонала" + + $motivationDataTableSort[$indMap[self::CODE_IT_SERVICES]][$column] = // "IT услуги" + $motivationDataTableSort[$indMap[self::CODE_ADMINISTRATION_OF_IT_INFRASTRUCTURE_CONNECTIONS_TO_DATABASES_SOFTWARE_MAIL_INTERNET]][$column] + // "Администрирование ИТ инфраструктуры (подключения к базам данных, ПО, почта, интернет)" + $motivationDataTableSort[$indMap[self::CODE_SOFTWARE_LICENSE_ERP_SYSTEM]][$column] + 0; // "Лицензия на ПО: ERP система" + + $motivationDataTableSort[$indMap[self::CODE_GENERAL_BUSINESS_EXPENSES]][$column] = // "Общехозяйственные расходы" + $motivationDataTableSort[$indMap[self::CODE_ACCOUNTING_AND_FINANCE]][$column] + // "Бухгалтерия и финансы" + $motivationDataTableSort[$indMap[self::CODE_LEGAL_SUPPORT]][$column] + // "Юридическое сопровождение" + $motivationDataTableSort[$indMap[self::CODE_HR_SERVICES]][$column] + // "HR- услуги" + $motivationDataTableSort[$indMap[self::CODE_IT_SERVICES]][$column] + // "IT услуги" + $motivationDataTableSort[$indMap[self::CODE_PROMOTION_AND_SALE_OF_GOODS_THROUGH_THE_WEBSITE]][$column] + 0; // "Продвижение и продажа товара через сайт" + + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT]][$column] = // "Чистая прибыль" + $motivationDataTableSort[$indMap[self::CODE_GROSS_PROFIT]][$column] - // "Валовая прибыль" + $motivationDataTableSort[$indMap[self::CODE_GENERAL_BUSINESS_EXPENSES]][$column] + 0; // "Общехозяйственные расходы" + + $c5 = $motivationDataTableSort[$indMap[self::CODE_REVENUE_FROM_SALES]][$column]; // "Выручка от реализации" + if ($c5 != 0) { + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_MARGIN_PERCENT]][$column] = // "Рентабельность по чистой прибыли, %" + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT]][$column] / $c5; // "Чистая прибыль" + } + + if ($ind == 0) { + $b62 = $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT]]["plan"]; // "Чистая прибыль" + if ($b62 > 0) { + $b64 = $b62 * 0.9; + } else { + $b64 = $b62 * 1.1; + } + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]]["plan"] = $b64; // "Минимальный порог Чистой прибыли, руб." + } + if ($ind >= 1 && $ind <= 4) { + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]][$column] = // "Минимальный порог Чистой прибыли, руб." + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]]["plan"] / $lastDayOfMonth * 7; // "Минимальный порог Чистой прибыли, руб." + } + if ($ind == 5) { + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]][$column] = // "Минимальный порог Чистой прибыли, руб." + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]]["plan"] / $lastDayOfMonth * ($lastDayOfMonth - 4 * 7); // "Минимальный порог Чистой прибыли, руб." + } + if ($ind == 7) { + $sum = 0; + foreach (range(1, 5) as $index) { + $sum += $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]]['week' . $index]; // "Минимальный порог Чистой прибыли, руб." + } + $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]][$column] = $sum; // "Минимальный порог Чистой прибыли, руб." + } + + // if ($ind > 0) { + // $c62 = $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT]][$column]; // "Чистая прибыль" + // $b64 = $motivationDataTableSort[$indMap[self::CODE_NET_PROFIT_THRESHOLD_RUB]][$ind == 6 ? "plan" : $column]; // "Минимальный порог Чистой прибыли, руб." + // + // $j66 = 0; + // if ($c62 >= $b64) { + // $formula = $c62 - $c5 * $motivationDataTableSort[$indMap[self::CODE_THRESHOLD_COEFFICIENT]]["plan"]; // "Пороговый коэффициент" + // if ($formula > 0) { + // $j66 = $formula; + // } + // } + // $motivationDataTableSort[$indMap[self::CODE_CALCULATION_OF_PREMIUM]][$column] = $j66 + 0; // "Расчет премии" + // } + } + + // Отклонение + $deviationFunc = function ($code) use(&$motivationDataTableSort, &$indMap) { + if ($motivationDataTableSort[$indMap[$code]]["plan"] != 0) { + $motivationDataTableSort[$indMap[$code]]["deviation"] = + $motivationDataTableSort[$indMap[$code]]["fact"] / + $motivationDataTableSort[$indMap[$code]]["plan"]; + } + }; + // $names = [ + // "Выручка от реализации", + // "Продажа товара", + // "Оффлайн продажи", + // "Онлайн продажи", + // "Прочие услуги", + // "Услуги по сборке", + // "Услуги по доставке", + // "Прямые расходы на продажу", + // "Себестоимость товара", + // "Услуги агентов (Расходы на закупку, хранение, доставку товара)", + // "Брак, пересорт", + // "Брак с поставки", + // "Списание неликвидного товара: порча, истечение срока годности", + // "Брак из-за поломки оборудования", + // "Пересорт", + // "Расходные материалы (обеспечение продаж)", + // "Маржинальный доход", + // "Операционные расходы (Себестоимость)", + // "Оплата труда", + // "Фонд оплаты труда персонала", + // "Содержание помещения", + // "Аренда", + // "Коммунальные услуги", + // "Охрана", + // "Услуги по уборке помещений и территории", + // "Расходы по доставке", + // "Доставка до клиента курьер", + // "Доставка до клиента такси", + // "Услуги маркетплейсов", + // "Содержание и обслуживание ОС и НМА", + // "Холодильное оборудование (ремонт, содержание, ТО)", + // "Расходы на содержание и ремонт оргтехники, в т.ч. расходные материалы", + // "Расходы на содержание и ремонт прочих ОС", + // "Техническое обслуживание кассовых аппаратов", + // "Услуги связи", + // "Интернет", + // "Прочие операционные расходы", + // "Хозяйственные товары", + // "Канцтовары", + // "Вода питьевая", + // "Валовая прибыль", + // "Общехозяйственные расходы", + // "Бухгалтерия и финансы", + // "Бухгалтерские услуги: постановка и ведение БУ и НУ", + // "Юридическое сопровождение", + // "Юридические услуги", + // "HR- услуги", + // "Кадровое администрирование, охрана труда", + // "Услуги по подбору персонала", + // "IT услуги", + // "Администрирование ИТ инфраструктуры (подключения к базам данных, ПО, почта, интернет)", + // "Лицензия на ПО: ERP система", + // "Продвижение и продажа товара через сайт", + // "Чистая прибыль", + // "Рентабельность по чистой прибыли, %" + // ]; + foreach (array_keys($indMap) as $code) { + $deviationFunc($code); + } + + return $motivationDataTableSort; + } ++ + /** + * Вычисление суммы себестоимости товара по магазину в определенный промежуток дат. + * + * @param string $startDate Дата начала недели (Y-m-d format). + * @param string $endDate Дата конца недели (Y-m-d format). + * @param int $storeId ID магазина. + * @return float Суммарная себестоимость товаров по магазину в определенный промежуток дат. + */ + public static function getSelfCostSumByStore($startDate, $endDate, $storeId) + { + + $storeId = (int)$storeId; + $startDate = date('Y-m-d', strtotime($startDate)); + $endDate = date('Y-m-d', strtotime($endDate)); + + + $sum = (float)SelfCostProduct::find() + ->where(['store_id' => $storeId]) + ->andWhere(['>=', 'date', $startDate]) + ->andWhere(['<=', 'date', $endDate]) + ->sum('price'); + + return $sum; + } + + /** + * Сохраняет мотивацию по себестоимости товара для указанного магазина и месяца. + * + * @param int $year Год в формате YYYY. + * @param int $month Месяц в формате MM. + * @param int $storeId ID магазина. + * @return bool Возвращает true в случае успешного сохранения данных, иначе false. + */ + public static function saveCostMotivation($storeId, $year, $month ) + { + // Получаем идентификатор мотивации + $motivation = Motivation::find() + ->where([ + 'store_id' => $storeId, + 'year' => $year, + 'month' => $month + ]) + ->one(); + + if (!$motivation) { + Yii::error("Мотивация не найдена для store_id=$storeId, year=$year, month=$month"); + return false; // мотивация не найдена, завершаем выполнение + } + + $motivationId = $motivation->id; + + // Вычисляем начало и конец месяца + $monthStart = date("Y-m-d 00:00:00", strtotime("$year-$month-01")); + $monthEnd = date("Y-m-t 23:59:59", strtotime("$year-$month-01")); + + // Переменная для отслеживания успешности сохранений + $success = true; + + // Проходим по каждой неделе (максимум 5 недель в месяце) + foreach (range(1, 5) as $weekIndex) { + // Вычисляем начало и конец недели + $weekStart = date("Y-m-d 00:00:00", strtotime("+" . (($weekIndex - 1) * 7) . " days", strtotime($monthStart))); + $weekEnd = date("Y-m-d 23:59:59", strtotime("+" . ($weekIndex * 7 - 1) . " days", strtotime($monthStart))); + + // Если конец недели выходит за пределы месяца, ограничиваем его концом месяца + if ($weekEnd > $monthEnd) { + $weekEnd = $monthEnd; + } + + Yii::info("Вычисляем себестоимость для недели $weekIndex: с $weekStart по $weekEnd"); + + // Рассчитываем сумму себестоимости за эту неделю + $costSum = self::getSelfCostSumByStore($weekStart, $weekEnd, $storeId); + + Yii::info("Сумма себестоимости для недели $weekIndex: $costSum"); + + // Определяем alias группы для недели (week1, week2 и т.д.) + $groupAlias = 'week' . $weekIndex; + + // Сохраняем или обновляем значение мотивации + $saveResult = self::saveOrUpdateMotivationValue( + $motivationId, + $groupAlias, + self::CODE_COSTS_OF_GOODS, // константа для себестоимости + 'float', + $costSum + ); + + if ($saveResult === false) { + Yii::error("Ошибка при сохранении значения для недели $weekIndex: $costSum"); + $success = false; + } + + // Прерываем цикл, если достигнут конец месяца + if ($weekEnd == $monthEnd) { + break; + } + } + + return $success; + } + }