From: Alexander Smirnov Date: Wed, 14 Aug 2024 12:31:48 +0000 (+0300) Subject: [ERP-140] Добавлен подсчёт зарплаты в факт X-Git-Tag: 1.4~5^2~30 X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=274b91247fd638ee094dbd744711f2208730fde7;p=erp24_rep%2Fyii-erp24%2F.git [ERP-140] Добавлен подсчёт зарплаты в факт --- diff --git a/erp24/actions/motivation/TestFactAction.php b/erp24/actions/motivation/TestFactAction.php index 87f915e4..97ba3397 100644 --- a/erp24/actions/motivation/TestFactAction.php +++ b/erp24/actions/motivation/TestFactAction.php @@ -55,6 +55,8 @@ class TestFactAction extends Action MotivationService::calculateMonthServices($model->year, $model->month); MotivationService::calculateMonthDefect($model->year, $model->month); + + MotivationService::calculateMonthSalary($model->year, $model->month); } return $this->controller->render('test-fact', compact('model', 'years', 'months')); diff --git a/erp24/scripts/tasks/task_32_motivation_fact.php b/erp24/scripts/tasks/task_32_motivation_fact.php index 334f46cf..496647a0 100644 --- a/erp24/scripts/tasks/task_32_motivation_fact.php +++ b/erp24/scripts/tasks/task_32_motivation_fact.php @@ -59,6 +59,7 @@ try { MotivationService::calculateMonthDefect($year, $month); + MotivationService::calculateMonthSalary($year, $month); ////////////////////////////////////////////// diff --git a/erp24/services/MotivationService.php b/erp24/services/MotivationService.php index 5e499e6d..b18e0541 100644 --- a/erp24/services/MotivationService.php +++ b/erp24/services/MotivationService.php @@ -2,8 +2,10 @@ namespace yii_app\services; +use Yii; use PhpOffice\PhpSpreadsheet\IOFactory; use yii\helpers\Json; +use yii_app\records\EmployeePayment; use yii_app\records\ExportImportTable; use yii\helpers\ArrayHelper; use yii_app\records\Motivation; @@ -11,6 +13,8 @@ use yii_app\records\MotivationValue; use yii_app\records\MotivationValueGroup; use yii_app\records\CityStore; use yii_app\records\MotivationCostsItem; +use yii_app\records\Timetable; +use yii_app\records\TimetableFactModel; use yii_app\records\WriteOffs; use yii_app\records\Products1c; use yii_app\records\ProductsClass; @@ -21,7 +25,15 @@ use yii_app\records\SalesProducts; class MotivationService { - + /** + * Получает ассоциативный массив алиасов групп мотивации. + * + * Этот метод извлекает все записи из таблицы `MotivationValueGroup`, а затем создает + * ассоциативный массив, где ключами являются идентификаторы групп, а значениями — их алиасы. + * + * @return array Ассоциативный массив, где ключами являются ID групп мотивации, + * а значениями — их алиасы. + */ private function getMotivationValueGroupAliases() { $groups = MotivationValueGroup::find()->all(); @@ -32,7 +44,20 @@ class MotivationService return $aliases; } - + /** + * Получает отсортированные данные мотивации для заданного магазина, года и месяца. + * + * Метод извлекает мотивационные данные, соответствующие указанному магазину, году и месяцу, + * а затем обрабатывает их для создания структурированного и отсортированного массива значений. + * Включает дополнительные элементы с нулевыми значениями для плановых и фактических данных, + * если они отсутствуют в основной таблице мотивации. + * + * @param int|null $storeId Идентификатор магазина. Если не указан, может использоваться значение по умолчанию. + * @param int|null $year Год. Если не указан, может использоваться значение по умолчанию. + * @param int|null $month Месяц. Если не указан, может использоваться значение по умолчанию. + * + * @return array Отсортированный массив данных мотивации, включая плановые, фактические и другие элементы. + */ public function getMotivationDataTableSort($storeId = null, $year = null, $month = null) { // 1. Запрос к таблице Motivation @@ -520,6 +545,292 @@ class MotivationService } } + /** + * Получение записей по фактическому количеству смен в магазине за указанный период. + * + * @param string $startDate Дата начала периода в формате 'YYYY-MM-DD'. + * @param string $endDate Дата окончания периода в формате 'YYYY-MM-DD'. + * @param int $store_id Идентификатор магазина. + * + * @return array|null Возвращает массив записей из модели TimetableFactModel, соответствующих условиям, + * или null, если записи не найдены. + */ + public static function getTimetableFactRecordsByDateAndStore($startDate, $endDate, $store_id) + { + + + // Делаем запрос к TimetableFactModel + $records = TimetableFactModel::find() + ->where(['store_id' => $store_id]) + ->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 $store_id Идентификатор магазина. + * + * @return float Возвращает общую сумму отпускных за указанный период. + * Если записей не найдено, возвращается 0.0. + */ + public static function getVacationsSum($startDate, $endDate, $store_id):float + { + // Делаем запрос к таблице Timetable для получения записей с slot_type_id = 2 + $records = Timetable::find() + + ->where(['store_id' => $store_id]) + ->andWhere(['between', 'date', $startDate, $endDate]) + ->andWhere(['slot_type_id' => 2]) + ->all(); + + // Проверяем, есть ли записи + if (empty($records)) { + return 0.0; // Возвращаем 0, если записей нет + } + + + + $vacationsSum = 0.0; + foreach ($records as $record) { + // Находим самую позднюю запись по admin_id + $payment = EmployeePayment::find() + ->where(['admin_id' => $record->admin_id]) + ->orderBy(['date' => SORT_DESC]) + ->one(); + $dailyPayment = $payment ? $payment->daily_payment : 0.0; + + + $vacationsSum += $dailyPayment; + + } + + + + return $vacationsSum; + } + + /** + * Получение суммы всех недель для определенного `value_id` из таблицы `motivation_value`. + * + * Метод выполняет запрос к модели `MotivationValue`, чтобы получить все записи, + * соответствующие указанным `motivation_id` и `value_id`, и принадлежащие одной из + * определенных групп мотивации. Затем метод суммирует значения поля `value_float` + * из всех полученных записей. + * + * @param int $id Идентификатор мотивации (`motivation_id`). + * @param int $value_id Идентификатор значения (`value_id`). + * + * @return float Возвращает сумму всех значений `value_float` для заданного `value_id`. + * Если записи не найдены, возвращается 0. + */ + public static function getMonthSum($id, $value_id) + { + // Запрос к модели MotivationValue для получения записей с заданными условиями + $records = MotivationValue::find() + ->where(['motivation_id' => $id, 'value_id' => $value_id]) + ->andWhere(['in', 'motivation_group_id', [1, 2, 3, 4, 5]]) + ->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 $motivation_id Идентификатор мотивации. + * @param int $group_id Идентификатор группы мотивации. + * @param int $value_id Идентификатор значения. + * @param string $value_type Тип значения, который может быть '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($motivation_id, $group_id, $value_id, $value_type, $value) + { + // Найти существующую запись + $motivationValue = MotivationValue::findOne([ + 'motivation_id' => $motivation_id, + 'motivation_group_id' => $group_id, + 'value_id' => $value_id, + ]); + + // Если запись не найдена, создать новую + if ($motivationValue === null) { + $motivationValue = new MotivationValue(); + $motivationValue->motivation_id = $motivation_id; + $motivationValue->motivation_group_id = $group_id; + $motivationValue->value_id = $value_id; + $motivationValue->value_type = $value_type; + } + + // Установить значение в зависимости от типа + switch ($value_type) { + 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("Неправильное значение типа: $value_type"); + } + + // Сохранить запись и вернуть id или false + if ($motivationValue->save()) { + return $motivationValue->id; + } else { + Yii::error("Не удалось сохранить значение: " . json_encode($motivationValue->errors)); + return false; + } + } + + public static function getMotivationValue($motivation_id, $group_id, $value_id) { + $value = 0; + $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivation_id, + 'motivation_group_id' => $group_id, 'value_id' => $value_id])->one(); + /** @var $motivationValue MotivationValue */ + if ($motivationValue) { + switch ($motivationValue->value_type) { + case MotivationCostsItem::DATA_TYPE_INT: { $value = $motivationValue->value_int; break; } + default: { $value = $motivationValue->value_float; break;} + } + } + return $value; + } + + /** + * Определяет номер недели в месяце для указанной даты. + * + * Метод вычисляет номер недели в месяце на основе дня месяца, переданного в объекте `DateTime`. + * Недели считаются следующим образом: + * - 1-я неделя: с 1-го по 7-й день месяца. + * - 2-я неделя: с 8-го по 14-й день месяца. + * - 3-я неделя: с 15-го по 21-й день месяца. + * - 4-я неделя: с 22-го по 28-й день месяца. + * - 5-я неделя: с 29-го дня месяца и до конца месяца. + * + * @param \DateTime $dateTime Объект `DateTime`, для которого нужно определить номер недели. + * + * @return int Номер недели в месяце (от 1 до 5). + */ + public static function getWeekOfMonth($dateTime) + { + $dayOfMonth = intval($dateTime->format('j')); + 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 \DateTime $dateTime Объект `DateTime`, представляющий дату, для которой определяется неделя. + * @param int $weekOfMonth Номер недели в месяце (от 1 до 5). + * + * @return \DateTime Дата начала указанной недели месяца. + */ + public static function getStartOfWeek($dateTime, $weekOfMonth) + { + $year = $dateTime->format('Y'); + $month = $dateTime->format('m'); + + switch ($weekOfMonth) { + case 1: + return new DateTime("$year-$month-01"); + case 2: + return new DateTime("$year-$month-08"); + case 3: + return new DateTime("$year-$month-15"); + case 4: + return new DateTime("$year-$month-22"); + case 5: + return new DateTime("$year-$month-29"); + default: + return new DateTime("$year-$month-01"); + } + } + + /** + * Вычисление общей суммы фонда оплаты труда (ФОТ) - зарплаты и отпускных для магазина за указанный период. + * + * Этот метод сначала получает записи о сменах сотрудников за указанный период и сумму отпускных. + * Затем для каждой записи определяется соответствующая дневная зарплата, и она добавляется к общей сумме. + * Если в записи уже указана зарплата за смену, она используется вместо дневной зарплаты. + * В конце к общей сумме зарплат добавляется сумма отпускных. + * + * @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); + + $totalSalary = 0.0; + foreach ($records as $record) { + // Находим самую позднюю запись по admin_id + $payment = EmployeePayment::find() + ->where(['admin_id' => $record->admin_id]) + ->orderBy(['date' => SORT_DESC]) + ->one(); + $dailyPayment = $payment ? $payment->daily_payment : 0.0; + + if (!empty($record->salary_shift)) { + $totalSalary += $record->salary_shift; + } else { + $totalSalary += $dailyPayment; + } + } + + return $totalSalary + $vacationSum; + } + public static function calculateMonthSales($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')); @@ -564,6 +875,8 @@ class MotivationService foreach ($storeIds as $store_id) { if (isset($motivations[$store_id])) { if (isset($salesOffline[$store_id])) { + $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemOffline->code); + $motivationValueOffline = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id, 'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemOffline->code])->one(); if (!$motivationValueOffline) { @@ -573,13 +886,15 @@ class MotivationService $motivationValueOffline->value_id = $motivationCostsItemOffline->code; $motivationValueOffline->value_type = $motivationCostsItemOffline->data_type; } - $motivationValueOffline->value_float = $salesOffline[$store_id]['total']; + $motivationValueOffline->value_float = $salesOffline[$store_id]['total'] + $correction; $motivationValueOffline->save(); if ($motivationValueOffline->getErrors()) { throw new \Exception(Json::encode($motivationValueOffline->getErrors())); } } if (isset($salesOnline[$store_id])) { + $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemOnline->code); + $motivationValueOnline = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id, 'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemOnline->code])->one(); if (!$motivationValueOnline) { @@ -589,7 +904,7 @@ class MotivationService $motivationValueOnline->value_id = $motivationCostsItemOnline->code; $motivationValueOnline->value_type = $motivationCostsItemOnline->data_type; } - $motivationValueOnline->value_float = $salesOnline[$store_id]['total']; + $motivationValueOnline->value_float = $salesOnline[$store_id]['total'] + $correction; $motivationValueOnline->save(); if ($motivationValueOnline->getErrors()) { throw new \Exception(Json::encode($motivationValueOnline->getErrors())); @@ -687,6 +1002,8 @@ class MotivationService foreach ($storeIds as $store_id) { if (isset($motivations[$store_id])) { if (isset($salesProduct[$store_id])) { + $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemServices->code); + $motivationValueService = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id, 'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemServices->code])->one(); if (!$motivationValueService) { @@ -696,13 +1013,15 @@ class MotivationService $motivationValueService->value_id = $motivationCostsItemServices->code; $motivationValueService->value_type = $motivationCostsItemServices->data_type; } - $motivationValueService->value_float = $salesProduct[$store_id]['total']; + $motivationValueService->value_float = $salesProduct[$store_id]['total'] + $correction; $motivationValueService->save(); if ($motivationValueService->getErrors()) { throw new \Exception(Json::encode($motivationValueService->getErrors())); } } if (isset($salesProductDelivery[$store_id])) { + $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItemServicesDelivery->code); + $motivationValueServiceDelivery = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id, 'motivation_group_id' => $motivationValueGroupFact->id, 'value_id' => $motivationCostsItemServicesDelivery->code])->one(); if (!$motivationValueServiceDelivery) { @@ -712,7 +1031,7 @@ class MotivationService $motivationValueServiceDelivery->value_id = $motivationCostsItemServicesDelivery->code; $motivationValueServiceDelivery->value_type = $motivationCostsItemServicesDelivery->data_type; } - $motivationValueServiceDelivery->value_float = $salesProductDelivery[$store_id]['total']; + $motivationValueServiceDelivery->value_float = $salesProductDelivery[$store_id]['total'] + $correction; $motivationValueServiceDelivery->save(); if ($motivationValueServiceDelivery->getErrors()) { throw new \Exception(Json::encode($motivationValueServiceDelivery->getErrors())); @@ -747,6 +1066,9 @@ class MotivationService } $motivationCostsItem = MotivationCostsItem::find()->where(['name' => $motivationItemType])->one(); /** @var $motivationCostsItem MotivationCostsItem */ + + $correction = self::getMotivationValue($motivations[$store_id]->id, 8, $motivationCostsItem->code); + $motivationValue = MotivationValue::find()->where(['motivation_id' => $motivations[$store_id]->id, 'motivation_group_id' => $motivationValueGroup->id, 'value_id' => $motivationCostsItem->code])->one(); if (!$motivationValue) { @@ -756,7 +1078,7 @@ class MotivationService $motivationValue->value_id = $motivationCostsItem->code; $motivationValue->value_type = $motivationCostsItem->data_type; } - $motivationValue->value_float = $data['total']; + $motivationValue->value_float = $data['total'] + $correction; $motivationValue->save(); if ($motivationValue->getErrors()) { throw new \Exception(Json::encode($motivationValue->getErrors())); @@ -765,4 +1087,23 @@ class MotivationService } } } + + public static function calculateMonthSalary($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')); + + $motivations = Motivation::find() + ->where(['year' => $year, 'month' => $month]) + ->all(); + + foreach ($motivations as $motivation) { + $store_id = $motivation->store_id; + $monthlyTotalSalary = self::calculateTotalSalary(date("Y-m-d", strtotime($monthStart)), date("Y-m-d", strtotime($monthEnd)), $store_id); + + $correction = self::getMotivationValue($motivation->id, 8, 11); + + self::saveOrUpdateMotivationValue($motivation->id, + 6, 11, "float", $monthlyTotalSalary + $correction); + } + } }