use yii_app\records\SalesProducts;
use yii_app\records\TimetableFactModel;
use yii_app\records\Timetable;
+use yii_app\records\WriteOffsProducts;
+
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;
+
/**
* Получает ассоциативный массив алиасов групп мотивации.
*
$result = [];
$groupAliases = self::getMotivationValueGroupAliases();
+ $consumable = self::getConsumable($motivation->id);
+
foreach ($motivationValues as $value) {
$valueId = $value->value_id;
$groupId = $value->motivation_group_id;
'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,
break;
}
- $result[$costsItem->order][$groupAlias] = $actualValue;
+ if ($value->value_id != self::CODE_CONSUMABLES_SALES_SUPPORT) {
+ $result[$costsItem->order][$groupAlias] = $actualValue;
+ }
}
// Сортировка результата по ключу (order)
$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;
}
$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()) {
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'));
foreach (MotivationCostsItem::getWriteOffsItems() as $key) {
$sum = 0;
- foreach($writeOffs as $data) {
+ foreach ($writeOffs as $data) {
if (($data['type'] ?? '') != $key) {
continue;
}
}
}
- 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'));
}
}
- 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'));
}
}
- 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');
$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[] */
$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'));
* @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])
->all();
// Получаем массив значений value_float
- $values = array_map(function($record) {
+ $values = array_map(function ($record) {
return $record->value_float;
}, $records);
* @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);
/**
* Возвращает массив, представляющий недели месяца (с указаниме начала и конца недели) для указанного года и месяца.
*
- * @param int|string $year Год в виде числа или строки (например, `2024` или `'2024'`).
+ * @param int|string $year Год в виде числа или строки (например, `2024` или `'2024'`).
* @param int|string $month Месяц в виде числа или строки (например, `2` или `'02'`).
*
* @return array Массив недель месяца, где каждая неделя представлена ассоциативным массивом с ключами 'start' и 'end'.
}
}
- public static function calculateFactFormula($motivationDataTableSort, $year, $month) {
+ public static function calculateFactFormula($motivationDataTableSort, $year, $month)
+ {
// Определяем последний день месяца
$lastDayOfMonth = date('t', strtotime("$year-$month-01"));
}
$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;
+ }
}
// Проверяем и инициализируем каждую запись, если она отсутствует
}
// Отклонение
- $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"] /
* @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()
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;
+
+ }
+
}