From: JoySystem_v Date: Thu, 15 Aug 2024 13:07:18 +0000 (+0300) Subject: Merge branch 'develop' into feature_fomichev_erp-91_create_method_sum_salary X-Git-Tag: 1.4~35^2~3 X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=b01444073471ee02df58e55d6417835563672ad8;p=erp24_rep%2Fyii-erp24%2F.git Merge branch 'develop' into feature_fomichev_erp-91_create_method_sum_salary # Conflicts: # erp24/services/MotivationService.php --- b01444073471ee02df58e55d6417835563672ad8 diff --cc erp24/services/MotivationService.php index 75083ea5,5622550a..6829c53d --- a/erp24/services/MotivationService.php +++ b/erp24/services/MotivationService.php @@@ -550,304 -520,68 +550,369 @@@ class MotivationServic } } + 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')); + + foreach (range(1, 5) as $ind) { + $weekStart = date("Y-m-d 00:00:00", strtotime("+" . (($ind - 1) * 7) . ' days', strtotime($monthStart))); + $weekEnd = date("Y-m-d 23:59:59", strtotime("+" . ($ind * 7 - 1) . ' days', strtotime($monthStart))); + if ($weekEnd > $monthEnd) { + $weekEnd = $monthEnd; + } + + $sales = Sales::find() + ->where(['between', 'date', $weekStart, $weekEnd]) + ->andWhere(['store_id' => $store_id]) + ->andWhere(['operation' => Sales::OPERATION_SALE]) + ->asArray()->all(); + $salesIds = ArrayHelper::getColumn($sales, 'id'); + + // Ищем чеки-возврат на текущие чеки + $returnSales = Sales::find()->where(['operation' => Sales::OPERATION_RETURN, 'sales_check' => $salesIds])->all(); + $returnSalesIds = ArrayHelper::getColumn($returnSales, 'sales_check'); + + // Offline sales + $salesOffline = Sales::find()->select(['SUM(summ) as total']) + ->where(['between', 'date', $weekStart, $weekEnd]) + ->andWhere(['order_id' => ['', '0']]) + ->andWhere(['store_id' => $store_id]) + ->andWhere(['operation' => Sales::OPERATION_SALE]) + ->andWhere(['NOT IN', 'id', $returnSalesIds]) + ->asArray()->one(); + + // Online sales + $salesOnline = Sales::find()->select(['SUM(summ) as total']) + ->where(['between', 'date', $weekStart, $weekEnd]) + ->andWhere(['NOT IN', 'order_id', ['', '0']]) + ->andWhere(['store_id' => $store_id]) + ->andWhere(['operation' => Sales::OPERATION_SALE]) + ->andWhere(['NOT IN', 'id', $returnSalesIds]) + ->asArray()->one(); + + $motivation = Motivation::find()->where(['store_id' => $store_id, 'year' => $year, 'month' => $month])->one(); + $motivationValueGroup = MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one(); + foreach (['Оффлайн продажи', 'Онлайн продажи'] as $topicInd => $topic) { + $motivationCostsItem = MotivationCostsItem::find()->where(['name' => $topic])->one(); + /** @var $motivationCostsItem MotivationCostsItem */ + if ($motivation) { + $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivation->id, + 'motivation_group_id' => $motivationValueGroup->id, 'value_id' => $motivationCostsItem->code])->one(); + if (!$motivationValue) { + $motivationValue = new MotivationValue; + $motivationValue->motivation_id = $motivation->id; + $motivationValue->motivation_group_id = $motivationValueGroup->id; + $motivationValue->value_id = $motivationCostsItem->code; + $motivationValue->value_type = $motivationCostsItem->data_type; + } + $motivationValue->value_float = [$salesOffline, $salesOnline][$topicInd]['total']; + $motivationValue->save(); + if ($motivationValue->getErrors()) { + throw new \Exception(Json::encode($motivationValue->getErrors())); + } + } + } + } + } ++ + /** + * Получение записей по фактическому количеству смен в магазине за указанный период. + * + * @param string $startDate Дата начала периода в формате 'YYYY-MM-DD'. + * @param string $endDate Дата окончания периода в формате 'YYYY-MM-DD'. + * @param int $storeId Идентификатор магазина. + * + * @return array|null Возвращает массив записей из модели TimetableFactModel, соответствующих условиям, + * или null, если записи не найдены. + */ + public static function getTimetableFactRecordsByDateAndStore($startDate, $endDate, $storeId) + { + + + // Делаем запрос к TimetableFactModel + $records = TimetableFactModel::find() + ->where(['store_id' => $storeId]) + ->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 $storeId Идентификатор магазина. + * + * @return float Возвращает общую сумму отпускных за указанный период. + * Если записей не найдено, возвращается 0.0. + */ + 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(); + + // Проверяем, есть ли записи + if (empty($records)) { + return 0.0; // Возвращаем 0, если записей нет + } + + $dailyPayments = self::getEmployeePayments($endDate); + + $vacationsSum = 0.0; + foreach ($records as $record) { + $dailyPayment = isset($dailyPayments[$record->admin_id]) ? $dailyPayments[$record->admin_id] : 0.0; + + + $vacationsSum += $dailyPayment; + + } + + return $vacationsSum; + } + + /** + * Получение суммы всех недель для определенного `value_id` из таблицы `motivation_value`. + * + * Метод выполняет запрос к модели `MotivationValue`, чтобы получить все записи, + * соответствующие указанным `motivation_id` и `value_id`, и принадлежащие одной из + * определенных групп мотивации. Затем метод суммирует значения поля `value_float` + * из всех полученных записей. + * + * @param int $id Идентификатор мотивации (`motivation_id`). + * @param int $valueId Идентификатор значения (`value_id`). + * + * @return float Возвращает сумму всех значений `value_float` для заданного `value_id`. + * Если записи не найдены, возвращается 0. + */ + public static function getMonthSum($id, $valueId) + { + $aliases = ['week1', 'week2', 'week3', 'week4', 'week5']; + $groupIds = MotivationCostsItem::find() + ->select('id') + ->where(['alias' => $aliases]) + ->column(); + // Запрос к модели MotivationValue для получения записей с заданными условиями + $records = MotivationValue::find() + ->where(['motivation_id' => $id, 'value_id' => $valueId]) + ->andWhere(['in', 'motivation_group_id', $groupIds]) + ->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 $motivationId Идентификатор мотивации. + * @param string $groupAlias Алиас группы мотивации. + * @param int $valueId Идентификатор значения. + * @param string $valueType Тип значения, который может быть '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($motivationId, $groupAlias, $valueId, $valueType, $value) + { + + // Найти id по алиасу в таблице MotivationValueGroup + $group = MotivationValueGroup::findOne(['alias' => $groupAlias]); + + if ($group === null) { + throw new \InvalidArgumentException("Неверный алиас группы: $groupAlias"); + } + + $groupId = $group->id; + // Найти существующую запись + $motivationValue = MotivationValue::findOne([ + 'motivation_id' => $motivationId, + 'motivation_group_id' => $groupId, + 'value_id' => $valueId, + ]); + + // Если запись не найдена, создать новую + if ($motivationValue === null) { + $motivationValue = new MotivationValue(); + $motivationValue->motivation_id = $motivationId; + $motivationValue->motivation_group_id = $groupId; + $motivationValue->value_id = $valueId; + $motivationValue->value_type = $valueType; + } + + // Установить значение в зависимости от типа + switch ($valueType) { + 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("Неправильное значение типа: $valueType"); + } + + // Сохранить запись и вернуть id или false + if ($motivationValue->save()) { + return $motivationValue->id; + } else { + Yii::error("Не удалось сохранить значение: " . json_encode($motivationValue->errors)); + return false; + } + } + + /** + * Определяет номер недели в месяце для указанной даты. + * + * Метод вычисляет номер недели в месяце на основе дня месяца, переданного в объекте `DateTime`. + * Недели считаются следующим образом: + * - 1-я неделя: с 1-го по 7-й день месяца. + * - 2-я неделя: с 8-го по 14-й день месяца. + * - 3-я неделя: с 15-го по 21-й день месяца. + * - 4-я неделя: с 22-го по 28-й день месяца. + * - 5-я неделя: с 29-го дня месяца и до конца месяца. + * + * @param string $date Строка даты в формате 'Y-m-d', для которой нужно определить номер недели. + * + * @return int Номер недели в месяце (от 1 до 5). + */ + public static function getWeekOfMonth($date) + { + // Преобразуем строку даты в день месяца + $dayOfMonth = intval(date('j', strtotime($date))); + + // Определяем номер недели в месяце + 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 string $date Строка даты в формате 'Y-m-d', представляющая дату, для которой определяется неделя. + * @param int $weekOfMonth Номер недели в месяце (от 1 до 5). + * + * @return string Дата начала указанной недели месяца в формате 'Y-m-d'. + */ + public static function getStartOfWeek($date, $weekOfMonth) + { + // Извлекаем год и месяц из строки даты + $year = date('Y', strtotime($date)); + $month = date('m', strtotime($date)); + + switch ($weekOfMonth) { + case 1: + return sprintf("%s-%s-01", $year, $month); + case 2: + return sprintf("%s-%s-08", $year, $month); + case 3: + return sprintf("%s-%s-15", $year, $month); + case 4: + return sprintf("%s-%s-22", $year, $month); + case 5: + return sprintf("%s-%s-29", $year, $month); + default: + throw new \InvalidArgumentException("Некорректное значение для недели месяца: $weekOfMonth"); + } + } + + /** + * Вычисление общей суммы фонда оплаты труда (ФОТ) - зарплаты и отпускных для магазина за указанный период. + * + * Этот метод сначала получает записи о сменах сотрудников за указанный период и сумму отпускных. + * Затем для каждой записи определяется соответствующая дневная зарплата, и она добавляется к общей сумме. + * Если в записи уже указана зарплата за смену, она используется вместо дневной зарплаты. + * В конце к общей сумме зарплат добавляется сумма отпускных. + * + * @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); + $dailyPayments = self::getEmployeePayments($endDate); + $totalSalary = 0.0; + foreach ($records as $record) { + $dailyPayment = isset($dailyPayments[$record->admin_id]) ? $dailyPayments[$record->admin_id] : 0.0; + + if (!empty($record->salary_shift)) { + $totalSalary += $record->salary_shift; + } else { + $totalSalary += $dailyPayment; + } + } + + return $totalSalary + $vacationSum; + } + + + /** + * @param $currentDate + * @return array + */ + public static function getEmployeePayments($currentDate): array + { + // Запрос для получения всех записей, но только с учетом последней даты для каждого admin_id + $employeePayments = EmployeePayment::find() + ->where(['<=', 'date', $currentDate]) + ->orderBy(['admin_id' => SORT_ASC, 'date' => SORT_DESC]) + ->all(); + + // Преобразование результатов в массив admin_id => daily_payment + $dailyPayments = []; + foreach ($employeePayments as $payment) { + if (!isset($dailyPayments[$payment->admin_id])) { + $dailyPayments[$payment->admin_id] = $payment->daily_payment; + } + } + return $dailyPayments; + } }