From: JoySystem_v Date: Wed, 14 Aug 2024 08:32:05 +0000 (+0300) Subject: Merge branch 'develop' into feature_fomichev_erp-91_create_method_sum_salary X-Git-Tag: 1.4~35^2~6 X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=26d329366ea22bd67c372806c45cb3feef10cbcb;p=erp24_rep%2Fyii-erp24%2F.git Merge branch 'develop' into feature_fomichev_erp-91_create_method_sum_salary # Conflicts: # erp24/services/MotivationService.php --- 26d329366ea22bd67c372806c45cb3feef10cbcb diff --cc erp24/services/MotivationService.php index e5321606,0ada3d5e..c689de43 --- a/erp24/services/MotivationService.php +++ b/erp24/services/MotivationService.php @@@ -2,20 -2,20 +2,28 @@@ namespace yii_app\services; +use Yii; use PhpOffice\PhpSpreadsheet\IOFactory; + use yii\helpers\Json; + use yii_app\records\ExportImportTable; + use yii\helpers\ArrayHelper; use yii_app\records\Motivation; use yii_app\records\MotivationValue; use yii_app\records\MotivationValueGroup; use yii_app\records\CityStore; use yii_app\records\MotivationCostsItem; + use yii_app\records\WriteOffs; + use yii_app\records\Products1c; + use yii_app\records\ProductsClass; + use yii_app\records\Sales; + use yii_app\records\SalesProducts; +use yii_app\records\TimetableFactModel; +use yii_app\records\Timetable; +use DateTime; + - use yii\helpers\ArrayHelper; ++ + +use yii_app\records\EmployeePayment; class MotivationService @@@ -355,275 -335,188 +364,460 @@@ return compact('errors'); } + 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')); + + $motivation = Motivation::find()->where(['store_id' => $store_id, 'year' => $year, 'month' => $month])->one(); + $exportImportTable = ExportImportTable::find()->select(['export_val'])->where(['entity' => 'city_store', 'entity_id' => $store_id, 'export_id' => 1])->one(); + /** @var $exportImportTable ExportImportTable */ + + 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; + } + if ($weekStart > $monthEnd) { + continue; + } + + $motivationValueGroup = MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one(); + + if ($exportImportTable) { + $writeOffs = WriteOffs::find()->select(['sum(summ) as total', 'type']) + ->where(['between', 'date', $weekStart, $weekEnd]) + ->andWhere(['store_id' => $exportImportTable->export_val]) + ->groupBy(['type']) + ->indexBy('type') + ->asArray()->all(); + + foreach ($writeOffs as $key => $data) { + $motivationItemType = MotivationCostsItem::writeOffsToMotivationItemMap($key); + if (empty($motivationItemType)) { + continue; + } + $motivationCostsItem = MotivationCostsItem::find()->where(['name' => $motivationItemType])->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 = $data['total']; + $motivationValue->save(); + if ($motivationValue->getErrors()) { + throw new \Exception(Json::encode($motivationValue->getErrors())); + } + } + } + } + } + + if ($motivation) { + $motivation->save(); + if ($motivation->getErrors()) { + throw new \Exception(Json::encode($motivation->getErrors())); + } + } + } + + 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')); + + // Ищем каталог-гуиды категории services + $productsClass = ProductsClass::find()->select(['category_id', 'tip']) + ->where(['tip' => 'services']) + ->indexBy('category_id') + ->asArray()->all(); + + // Ищем продуктовые гуиды по каталог-гуидам категории service + $products1c = Products1c::find()->select(['id', 'parent_id', 'name']) + ->where(['parent_id' => array_keys($productsClass), 'tip' => 'products']) + ->indexBy(['id']) + ->asArray()->all(); + + // Ищем каталог-гуиды категории services_delivery + $productsClassDelivery = ProductsClass::find()->select(['category_id', 'tip']) + ->where(['tip' => 'services_delivery']) + ->indexBy('category_id') + ->asArray()->all(); + + // Ищем продуктовые гуиды по каталог-гуидам категории service_delivery + $products1cDelivery = Products1c::find()->select(['id', 'parent_id', 'name']) + ->where(['parent_id' => array_keys($productsClassDelivery), 'tip' => 'products']) + ->indexBy(['id']) + ->asArray()->all(); + + 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; + } + + // Ищем продажи по продуктовым гуидам по каталог-гуидам категории service + $sales = Sales::find()->alias('s') + ->leftJoin('sales_products p', 'p.check_id = s.id') + ->where(['between', 's.date', $weekStart, $weekEnd]) + ->andWhere(['p.product_id' => array_keys($products1c)]) + ->andWhere(['s.store_id' => $store_id]) + ->andWhere(['s.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'); + + // Ищем продукты из категории services + $salesProduct = SalesProducts::find() + ->select(['SUM(summ) as total']) + ->where(['check_id' => $salesIds]) + ->andWhere(['NOT IN', 'check_id', $returnSalesIds]) + ->andWhere(['product_id' => array_keys($products1c)]) + ->asArray()->one(); + + $motivationValueGroup = MotivationValueGroup::find()->where(['alias' => 'week' . $ind])->one(); + $motivationCostsItem = MotivationCostsItem::find()->where(['name' => 'Услуги по сборке'])->one(); + /** @var $motivationCostsItem MotivationCostsItem */ + $motivation = Motivation::find()->where(['store_id' => $store_id, 'year' => $year, 'month' => $month])->one(); + 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 = $salesProduct['total']; + $motivationValue->save(); + //var_dump($motivationValue); die; + if ($motivationValue->getErrors()) { + throw new \Exception(Json::encode($motivationValue->getErrors())); + } + } + + // Ищем продажи по продуктовым гуидам по каталог-гуидам категории service_delivery + $sales = Sales::find()->alias('s') + ->leftJoin('sales_products p', 'p.check_id = s.id') + ->where(['between', 's.date', $weekStart, $weekEnd]) + ->andWhere(['p.product_id' => array_keys($products1cDelivery)]) + ->andWhere(['s.store_id' => $store_id]) + ->andWhere(['s.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'); + + // Ищем продукты из категории services_delivery + $salesProduct = SalesProducts::find() + ->select(['SUM(summ) as total']) + ->where(['check_id' => $salesIds]) + ->andWhere(['NOT IN', 'check_id', $returnSalesIds]) + ->andWhere(['product_id' => array_keys($products1c)]) + ->asArray()->one(); + + $motivationCostsItem = MotivationCostsItem::find()->where(['name' => 'Услуги по доставке'])->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 = $salesProduct['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 $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; + } + } + + /** + * Определяет номер недели в месяце для указанной даты. + * + * Метод вычисляет номер недели в месяце на основе дня месяца, переданного в объекте `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; + } }