From: fomichev Date: Wed, 6 May 2026 12:33:18 +0000 (+0300) Subject: Правки по ревью X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=d471e0abb8a8dce98f8864a1d92dfa113121635e;p=erp24_rep%2Fyii-erp24%2F.git Правки по ревью --- diff --git a/erp24/api2/controllers/DataTestController.php b/erp24/api2/controllers/DataTestController.php index 9d6f8267..0b272dc4 100644 --- a/erp24/api2/controllers/DataTestController.php +++ b/erp24/api2/controllers/DataTestController.php @@ -1,27 +1,38 @@ response->format = \yii\web\Response::FORMAT_JSON; $request = Yii::$app->request->getRawBody(); try { $result = Json::decode($request); - } catch (Exception $ex) { + } catch (Exception) { return $this->asJson(['error' => ['code' => 400, 'message' => 'Json body invalid']]); } @@ -45,39 +56,34 @@ class DataTestController extends BaseController { $apiCronTest->status = 1; $apiCronTest->date_up = date("Y-m-d H:i:s"); - $validate = $apiCronTest->validate(); - if ($validate) { + if ($apiCronTest->validate()) { $apiCronTest->save(); } } } catch (Exception $e) { - + Yii::error($e->getMessage(), 'api2.data-test'); + return $this->asJson(["error_id" => 2, "error" => "Internal server error"]); } return $this->asJson(json_decode($json, true)); } - public function actionUpload() { - set_time_limit(600); + public function actionUpload() + { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; $request = Yii::$app->request->getRawBody(); try { $result = Json::decode($request); - } catch (Exception $ex) { + } catch (Exception) { return $this->asJson(['error' => ['code' => 400, 'message' => 'Json body invalid']]); } - $requestId = ''; - if (!empty($result["request_id"])) { - $requestId = $result["request_id"]; - } + $requestId = !empty($result["request_id"]) ? $result["request_id"] : ''; try { - - $apiCronTest = new ApiCronTest(); $apiCronTest->date = date("Y-m-d H:i:s"); $apiCronTest->date_up = date("Y-m-d H:i:s"); @@ -85,13 +91,16 @@ class DataTestController extends BaseController { $apiCronTest->json_post = $request; $apiCronTest->request_id = $requestId; $apiCronTest->direct_id = 2; - $validate = $apiCronTest->validate(); - if ($validate) { - $apiCronTest->save(); - } + if ($apiCronTest->validate()) { + if (!$apiCronTest->save()) { + Yii::error('ApiCronTest save failed: ' . Json::encode($apiCronTest->errors), 'api2.data-test'); + return $this->asJson(['success' => false, 'error' => 'Internal server error']); + } + } } catch (Exception $e) { - + Yii::error($e->getMessage(), 'api2.data-test'); + return $this->asJson(['success' => false, 'error' => 'Internal server error']); } return $this->asJson(['response' => true]); @@ -101,11 +110,12 @@ class DataTestController extends BaseController { * Возвращает JSON со списком магазинов (id, name) * GET /api2/data-test/get-stores */ - public function actionGetStores() { + public function actionGetStores() + { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; try { - $stores = \yii_app\records\CityStore::find() + $stores = CityStore::find() ->select(['id', 'name']) ->orderBy(['name' => SORT_ASC]) ->asArray() @@ -116,9 +126,10 @@ class DataTestController extends BaseController { 'data' => $stores ]); } catch (Exception $e) { + Yii::error($e->getMessage(), 'api2.data-test'); return $this->asJson([ 'success' => false, - 'error' => $e->getMessage() + 'error' => 'Internal server error' ]); } } @@ -139,22 +150,21 @@ class DataTestController extends BaseController { * 1 = с 8:00 до 20:00 (день) * 2 = с 20:00 до 8:00 (ночь) */ - public function actionGetSales() { - set_time_limit(300); + public function actionGetSales() + { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; $request = Yii::$app->request->getRawBody(); try { $input = Json::decode($request); - } catch (Exception $ex) { + } catch (Exception) { return $this->asJson([ 'success' => false, 'error' => 'Invalid JSON body' ]); } - // Валидация обязательных параметров if (empty($input['date'])) { return $this->asJson([ 'success' => false, @@ -162,132 +172,139 @@ class DataTestController extends BaseController { ]); } - $date = $input['date']; - $shiftType = $input['shift_type'] ?? 0; + $date = $input['date']; + $shiftType = $input['shift_type'] ?? 0; $storeIdFilter = $input['store_id'] ?? null; try { - // Определяем диапазон времени для смены - $dateObj = new \DateTime($date); + $dateObj = new \DateTime($date); $currentDateStr = $dateObj->format('Y-m-d'); + $nextDateStr = (clone $dateObj)->modify('+1 day')->format('Y-m-d'); - // Получаем дату следующего дня - $nextDateObj = clone $dateObj; - $nextDateStr = $nextDateObj->modify('+1 day')->format('Y-m-d'); - - $dayStart = $currentDateStr . ' 08:00:00'; - $dayEnd = $currentDateStr . ' 20:00:00'; - $nightStart = $currentDateStr . ' 20:00:00'; - $nightEnd = $currentDateStr . ' 23:59:59'; - $nextDayStart = $nextDateStr . ' 00:00:00'; - $nextDayEnd = $nextDateStr . ' 08:00:00'; - - // Построение базового запроса продаж - $query = \yii_app\records\Sales::find() - ->where(['or', ['order_id' => ''], ['order_id' => 0]]) // Только офлайн продажи + $query = Sales::find() + ->where(['or', ['order_id' => ''], ['order_id' => 0]]) ->orderBy(['store_id' => SORT_ASC, 'date' => SORT_ASC]); - // Фильтр по смене if ($shiftType === 1) { - // День: 8:00 - 20:00 - $query->andWhere(['between', 'date', $dayStart, $dayEnd]); + $query->andWhere(['between', 'date', $currentDateStr . ' 08:00:00', $currentDateStr . ' 20:00:00']); } elseif ($shiftType === 2) { - // Ночь: 20:00 - 8:00 следующего дня $query->andWhere(['OR', - ['between', 'date', $nightStart, $nightEnd], - ['between', 'date', $nextDayStart, $nextDayEnd] + ['between', 'date', $currentDateStr . ' 20:00:00', $currentDateStr . ' 23:59:59'], + ['between', 'date', $nextDateStr . ' 00:00:00', $nextDateStr . ' 08:00:00'], ]); } else { - // Все смены (вся дата) $query->andWhere(['between', 'date', $currentDateStr . ' 00:00:00', $currentDateStr . ' 23:59:59']); } - // Фильтр по магазину если задан if (!empty($storeIdFilter)) { $query->andWhere(['store_id' => $storeIdFilter]); } - $sales = $query->with('products')->asArray()->all(); + $sales = $query->asArray()->all(); if (empty($sales)) { - return $this->asJson([ - 'success' => true, - 'data' => [] - ]); + return $this->asJson(['success' => true, 'data' => []]); } - // Агрегируем данные и присоединяем информацию о товарах - $aggregated = []; - $storeRegions = []; + // Bulk-load store regions (avoid per-sale query) + $storeIds = array_unique(array_column($sales, 'store_id')); + $storeParamRows = CityStoreParams::find() + ->select(['store_id', 'address_region']) + ->where(['store_id' => $storeIds]) + ->asArray() + ->all(); + $storeRegions = array_column($storeParamRows, 'address_region', 'store_id'); - foreach ($sales as $sale) { - $saleStoreId = $sale['store_id']; + // Bulk-load all products for all sales at once + $saleIds = array_column($sales, 'id'); + $allSaleProducts = SalesProducts::find() + ->where(['check_id' => $saleIds]) + ->asArray() + ->all(); + + $productsBySale = []; + foreach ($allSaleProducts as $sp) { + $productsBySale[$sp['check_id']][] = $sp; + } + + // Bulk-load product names + $allProductIds = array_unique(array_column($allSaleProducts, 'product_id')); + $productInfoMap = []; + if (!empty($allProductIds)) { + $productInfoMap = Products1c::find() + ->select(['id', 'name']) + ->where(['id' => $allProductIds]) + ->indexBy('id') + ->asArray() + ->all(); + } - // Кэшируем регион магазина - if (!isset($storeRegions[$saleStoreId])) { - $storeParams = \yii_app\records\CityStoreParams::findOne(['store_id' => $saleStoreId]); - $storeRegions[$saleStoreId] = $storeParams ? $storeParams->address_region : null; + // Bulk-load active prices; keep latest per (product_id, region_id) + $pricesByProduct = []; + if (!empty($allProductIds)) { + $priceRows = PricesDynamic::find() + ->select(['product_id', 'region_id', 'price']) + ->where(['product_id' => $allProductIds, 'active' => PricesDynamic::ACTIVE]) + ->orderBy(['date_from' => SORT_DESC]) + ->asArray() + ->all(); + + foreach ($priceRows as $pr) { + $pid = $pr['product_id']; + $rid = $pr['region_id']; // null → '' as PHP array key + if (!isset($pricesByProduct[$pid][$rid])) { + $pricesByProduct[$pid][$rid] = (float)$pr['price']; + } + // Any-region fallback: first = latest + if (!isset($pricesByProduct[$pid]['_latest'])) { + $pricesByProduct[$pid]['_latest'] = (float)$pr['price']; + } } + } - $saleKey = $saleStoreId . '_' . $sale['date']; // Ключ для агрегации + $aggregated = []; + + foreach ($sales as $sale) { + $saleStoreId = $sale['store_id']; + $regionId = $storeRegions[$saleStoreId] ?? null; + $saleKey = $saleStoreId . '_' . $sale['date']; if (!isset($aggregated[$saleKey])) { $aggregated[$saleKey] = [ - 'id' => $sale['id'], - 'date' => $sale['date'], - 'store_id' => (int)$saleStoreId, - 'summ' => (float)$sale['summ'], + 'id' => $sale['id'], + 'date' => $sale['date'], + 'store_id' => (int)$saleStoreId, + 'summ' => (float)$sale['summ'], 'operation' => $sale['operation'], - 'status' => $sale['status'], - 'skidka' => (float)$sale['skidka'], - 'products' => [] + 'status' => $sale['status'], + 'skidka' => (float)$sale['skidka'], + 'products' => [] ]; } else { - // Суммируем значения - $aggregated[$saleKey]['summ'] += (float)$sale['summ']; + $aggregated[$saleKey]['summ'] += (float)$sale['summ']; $aggregated[$saleKey]['skidka'] += (float)$sale['skidka']; } - // Получаем товары из этого чека - $products = \yii_app\records\SalesProducts::find() - ->where(['check_id' => $sale['id']]) - ->asArray() - ->all(); + foreach ($productsBySale[$sale['id']] ?? [] as $product) { + $pid = $product['product_id']; - foreach ($products as $product) { - // Получаем информацию о товаре - $productInfo = \yii_app\records\Products1c::find() - ->where(['id' => $product['product_id']]) - ->select(['id', 'name']) - ->asArray() - ->one(); - - // Получаем актуальную цену товара - $regionId = $storeRegions[$saleStoreId]; - $priceQuery = \yii_app\records\PricesDynamic::find() - ->where(['product_id' => $product['product_id']]) - ->andWhere(['active' => \yii_app\records\PricesDynamic::ACTIVE]); - - // Приоритет: сначала ищем с регионом, потом без - if ($regionId) { - $priceQuery->andWhere(['region_id' => $regionId]); + // Region price → any-region latest → sale line price + if ($regionId !== null && isset($pricesByProduct[$pid][$regionId])) { + $price = $pricesByProduct[$pid][$regionId]; + } elseif (isset($pricesByProduct[$pid]['_latest'])) { + $price = $pricesByProduct[$pid]['_latest']; + } else { + $price = (float)$product['price']; } - $priceData = $priceQuery->select(['price']) - ->orderBy(['date_from' => SORT_DESC]) - ->asArray() - ->one(); - - $productData = [ - 'product_id' => $product['product_id'], - 'name' => $productInfo['name'] ?? 'Unknown', - 'quantity' => (float)$product['quantity'], - 'price' => $priceData ? (float)$priceData['price'] : (float)$product['price'], - 'discount' => (float)$product['discount'], - 'summ' => (float)$product['summ'] + $aggregated[$saleKey]['products'][] = [ + 'product_id' => $pid, + 'name' => $productInfoMap[$pid]['name'] ?? 'Unknown', + 'quantity' => (float)$product['quantity'], + 'price' => $price, + 'discount' => (float)$product['discount'], + 'summ' => (float)$product['summ'] ]; - - $aggregated[$saleKey]['products'][] = $productData; } } @@ -295,7 +312,6 @@ class DataTestController extends BaseController { $grouped = []; foreach ($aggregated as $sale) { $storeId = $sale['store_id']; - // Убираем store_id из объекта продажи, так как он будет в ключе unset($sale['store_id']); $grouped[$storeId][] = $sale; } @@ -306,9 +322,10 @@ class DataTestController extends BaseController { ]); } catch (Exception $e) { + Yii::error($e->getMessage(), 'api2.data-test'); return $this->asJson([ 'success' => false, - 'error' => $e->getMessage() + 'error' => 'Internal server error' ]); } } @@ -350,7 +367,7 @@ class DataTestController extends BaseController { try { $input = Json::decode($request); - } catch (Exception $ex) { + } catch (Exception) { return $this->asJson(['success' => false, 'error' => 'Invalid JSON body']); } @@ -362,32 +379,30 @@ class DataTestController extends BaseController { return $this->asJson(['success' => false, 'error' => 'date is required']); } - $storeId = (int) $input['store_id']; + $storeId = (int)$input['store_id']; $date = $input['date']; - $shiftType = (int) ($input['shift_type'] ?? 0); + $shiftType = (int)($input['shift_type'] ?? 0); try { - $dateObj = new \DateTime($date); - $nextDateStr = (clone $dateObj)->modify('+1 day')->format('Y-m-d'); + $nextDateStr = (new \DateTime($date))->modify('+1 day')->format('Y-m-d'); + // Только фактически работавшие (не отпуск, не больничный, не выходной) $query = TimetableFact::find() - ->select(['timetable.admin_id']) - ->andWhere(['store_id' => $storeId]); + ->select(['admin_id']) + ->andWhere(['store_id' => $storeId]) + ->andWhere(['slot_type_id' => [Timetable::TIMESLOT_WORK, Timetable::TIMESLOT_INTERNSHIP]]); if ($shiftType === 1) { - // Дневная: 08:00 – 20:00 $query->andWhere(['between', 'datetime_start', $date . ' 08:00:00', $date . ' 19:59:59', ]); } elseif ($shiftType === 2) { - // Ночная: 20:00 – 08:00 следующего дня $query->andWhere(['OR', ['between', 'datetime_start', $date . ' 20:00:00', $date . ' 23:59:59'], ['between', 'datetime_start', $nextDateStr . ' 00:00:00', $nextDateStr . ' 07:59:59'], ]); } else { - // Все смены за дату $query->andWhere(['between', 'datetime_start', $date . ' 00:00:00', $date . ' 23:59:59', @@ -402,19 +417,17 @@ class DataTestController extends BaseController { $adminIds = array_unique(array_column($rows, 'admin_id')); - // Получаем данные сотрудников (имя, должность) - $admins = \yii_app\records\Admin::find() + $admins = Admin::find() ->select(['id', 'name_full', 'employee_position_id']) ->where(['id' => $adminIds]) ->indexBy('id') ->asArray() ->all(); - // Получаем названия должностей - $positionIds = array_filter(array_column($admins, 'employee_position_id')); - $positions = []; + $positionIds = array_filter(array_unique(array_column($admins, 'employee_position_id'))); + $positions = []; if (!empty($positionIds)) { - $positions = \yii_app\records\EmployeePosition::find() + $positions = EmployeePosition::find() ->select(['id', 'name']) ->where(['id' => $positionIds]) ->indexBy('id') @@ -422,7 +435,6 @@ class DataTestController extends BaseController { ->all(); } - // Получаем текущий грейд (активная запись: closed_at = '2100-01-01 00:00:00') $gradeRows = AdminGradeHistory::find() ->alias('agh') ->select(['agh.admin_id', 'g.id AS grade_id', 'g.name AS grade_name']) @@ -434,7 +446,6 @@ class DataTestController extends BaseController { $grades = array_column($gradeRows, null, 'admin_id'); - // Получаем seller_id (GUID из 1С) для каждого сотрудника $exportRows = ExportImportTable::find() ->select(['entity_id', 'export_val']) ->where([ @@ -467,7 +478,8 @@ class DataTestController extends BaseController { return $this->asJson(['success' => true, 'data' => $result]); } catch (Exception $e) { - return $this->asJson(['success' => false, 'error' => $e->getMessage()]); + Yii::error($e->getMessage(), 'api2.data-test'); + return $this->asJson(['success' => false, 'error' => 'Internal server error']); } } }