]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
удаление
authorVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Fri, 6 Mar 2026 12:57:46 +0000 (15:57 +0300)
committerVVF <developer@DeepBlue.localdomain>
Fri, 6 Mar 2026 13:09:21 +0000 (16:09 +0300)
erp24/api2/controllers/BonusController.php
plan_email.md [deleted file]

index c54ec8a706ff794e940a3b89b79f89fc0985caea..6dc2a9f879bab1bfc439899816417f0d99d4018e 100644 (file)
-<?php\r
-\r
-namespace app\controllers;\r
-\r
-use yii_app\jobs\SendBonusInfoToSiteJob;\r
-use DateTime;\r
-use DateTimeZone;\r
-use Yii;\r
-use yii\helpers\ArrayHelper;\r
-use yii\helpers\Json;\r
-use yii_app\helpers\ClientHelper;\r
-use yii_app\records\BonusLevels;\r
-use yii_app\records\Contest001;\r
-use yii_app\records\ExportImportTable;\r
-use yii_app\records\MessagerUser;\r
-use yii_app\records\NotifiableUser;\r
-use yii_app\records\Products1c;\r
-use yii_app\records\Sales;\r
-use yii_app\records\Timetable;\r
-use yii_app\records\UniversalCatalogItem;\r
-use yii_app\records\UserBonusSendToTgLogs;\r
-use yii_app\records\Users;\r
-use yii_app\records\UsersAuthCallLog;\r
-use yii_app\records\UsersBonus;\r
-use yii_app\records\UsersBonusLevels;\r
-use yii_app\records\UsersEvents;\r
-use yii_app\records\UsersPhones;\r
-use yii_app\records\UsersStopList;\r
-use yii_app\records\Promocode;\r
-use yii_app\services\LogService;\r
-use yii_app\services\SiteService;\r
-\r
-class BonusController extends BaseController\r
-{\r
-    private const LOG_CATEGORY_BONUS = 'bonus.auth';\r
-\r
-    private static $YEAR_PERIOD = 366;\r
-    private static $FIRST_SALE_PROCENT = 0.1;\r
-    private static $SECOND_SALE_PROCENT = 0.15;\r
-    private static $MAX_PROCENT = 0.2;\r
-    private static $CREDIT_PROCENT = 0.1;\r
-    private static $CREDIT_HIGH_PROCENT = 0.3;\r
-    private static $CREDIT_HIGH_PROCENT_PART20 = 0.2;\r
-\r
-    const OUT_DIR =\r
-//        "/tmp";\r
-        "/var/www/erp24/api2/json"; // "/www/api2/json";\r
-// __DIR__ . "/../json"; //local\r
-\r
-    public function actionGetBonuses()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $fl = date('_Y_m_d__H_i_s_');\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . $result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        $__API_PARAMS = ['store_id', 'seller_id', 'phone']; // check_amount, items\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 0, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error_id" => 0, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 0.2, "error" => "phone is required"]);\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $result['phone'] = $phone;\r
-\r
-        $check_amount = intval($result['check_amount'] ?? 0);\r
-\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();\r
-        $bonusLevels = BonusLevels::find()->where(['active' => 1])->indexBy('alias')->asArray()->all();\r
-        $bonusLevel = $user->bonus_level ?? "silver";\r
-\r
-        $bonus_rate = isset($bonusLevels[$bonusLevel]['bonus_rate'])\r
-            ? $bonusLevels[$bonusLevel]['bonus_rate'] / 100\r
-            : self::$FIRST_SALE_PROCENT;\r
-\r
-        $cashback_rate = isset($bonusLevels[$bonusLevel]['cashback_rate'])\r
-            ? $bonusLevels[$bonusLevel]['cashback_rate'] / 100\r
-            : self::$FIRST_SALE_PROCENT;\r
-\r
-        $mess = [];\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        // массив с id товарыми не участвующих в бонусной\r
-        $items_arr_no = array_values(ArrayHelper::map(\r
-            UniversalCatalogItem::find()->where(['catalog_alias' => 'unused_nomenclature'])->all(), 'guid', 'guid'));\r
-        $items_arr_no_bonus_writeoffs = array_values(ArrayHelper::map(\r
-            UniversalCatalogItem::find()->where(['catalog_alias' => 'non_bonusable_goods'])->all(), 'guid', 'guid'));\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        $all_amount = 0;\r
-        $has_actions = false;\r
-        $summa_no = 0;\r
-        $summa_no_writeoffs = 0;\r
-        if (!empty($result["items"])) {\r
-            foreach ($result["items"] as $item) {\r
-                if (in_array($item["product_id"], $items_arr_no)) {\r
-                    $summa_no = $summa_no + $item["price"] * $item["quantity"];\r
-                    $has_actions = true;\r
-                } elseif (in_array($item["product_id"], $items_arr_no_bonus_writeoffs)) {\r
-                    $summa_no_writeoffs = $summa_no_writeoffs + $item["price"] * $item["quantity"];\r
-                }\r
-                $all_amount += $item["price"] * $item["quantity"];\r
-            }\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $baza_nachislenie = $all_amount - $summa_no;\r
-\r
-        $check_amount = $check_amount - $summa_no;\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        //$cnt = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])->count());\r
-        //$max_procent = $cnt == 0 ? self::$FIRST_SALE_PROCENT : ($cnt == 1 ? self::$SECOND_SALE_PROCENT : self::$MAX_PROCENT);\r
-        $max_procent = $bonus_rate;\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $percent = ($result['phone'] == "79049031399") ? 0.9 : $max_procent;\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $userFound = Users::find()->where(['phone' => $result['phone']])->one();\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        /** @var $userFound Users */\r
-        $salesCount = -1; /* Из-за нулевого значения по умолчанию куча клиентов получило бонус 20% за покупку */\r
-        if ($userFound && $userFound->telegram_created_at) {\r
-            $salesCount = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])\r
-                ->andWhere(['>=', 'date', $userFound->telegram_created_at])->count());\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $credit_procent = $userFound && $userFound->source > 0 && $salesCount == 0 ? self::$CREDIT_HIGH_PROCENT : $cashback_rate;\r
-        $will_be_credited_bonuses = $credit_procent * $baza_nachislenie;\r
-\r
-        $will_be_credited_bonuses = round($will_be_credited_bonuses);\r
-        $mess["will_be_credited_bonuses"] = $will_be_credited_bonuses;\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $store_id = ClientHelper::getExportId($result['store_id'], "city_store", 1);\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        // Логи введённых номеров телефонов кассирами\r
-        $userPhone = UsersPhones::find()->where(['phone' => $result['phone']])->andWhere(['store_id' => $store_id])\r
-            ->andWhere(['seller_id' => $result['seller_id']])->one();\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!$userPhone) {\r
-            $userPhone = new UsersPhones();\r
-            $userPhone->phone = $result['phone'];\r
-            $userPhone->store_id = $store_id;\r
-            $userPhone->store_guid = $result['store_id'];\r
-            $userPhone->seller_id = $result['seller_id'];\r
-        }\r
-        if (!$userPhone->store_guid) {\r
-            $userPhone->store_guid = $result['store_id'];\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $userPhone->date = date('Y-m-d H:i:s');\r
-        $userPhone->save();\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($userPhone->getErrors()) {\r
-            file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => $userPhone->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-            return $this->asJson(["error_id" => 1, "error" => $userPhone->getErrors()]);\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!$user) {\r
-            file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '-нет в бонусной программе-' . __LINE__, FILE_APPEND);\r
-            $mess["new_client"] = true;\r
-            $mess["message_cashier"] = "Заполните данные клиента";\r
-            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $keycode = $user->keycode;\r
-        $black_list = $user->black_list;\r
-\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!$black_list) {\r
-            file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $stop = UsersStopList::find()->select(['phone'])->where(['phone' => $result['phone']])->one();\r
-            if ($stop) {\r
-                $black_list = 1;\r
-                $user->black_list = 1;\r
-                file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $user->save();\r
-                file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($user->getErrors()) {\r
-                    file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                    return $this->asJson(["error_id" => 3, "error" => $user->getErrors()]);\r
-                }\r
-            }\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $name = $user->name;\r
-        $user_balans = ClientHelper::getBonusBalance($result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $baza_spisanie = $baza_nachislenie - $summa_no_writeoffs;\r
-        if ($baza_spisanie < 0) {\r
-            $baza_spisanie = 0;\r
-        }\r
-        $max = $baza_spisanie * $percent;  // максимально можем разрешить списывать до 30 процентов от суммы заказа\r
-        $max = round($max);\r
-        $available_bonus = $user_balans;\r
-        if ($available_bonus > $max) { // если баллов бонусов больше чем 30 процентов списываем по максимуму 30\r
-            $available_bonus = $max;\r
-        }\r
-//        $baza = $check_amount - $bonus;\r
-\r
-        $mess["message_cashier"] = "Клиент $name найден"; // Код: $keycode\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($black_list) {\r
-            $mess['error'] = 'Этот номер в черном списке';\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-\r
-        $txt = $has_actions ? 'В чеке есть акционные товары, на них бонусы не начислятся.' : '';\r
-\r
-        $mess["result"] = true;\r
-        $mess["auth_code"] = $keycode;\r
-        $mess["name"] = $name;\r
-        $mess["total_bonuses"] = $user_balans;\r
-        $mess["bonus_level"] = $bonusLevel;\r
-        $mess["burn_balans"] = $user->burn_balans;\r
-        $mess["available_bonuses"] = $available_bonus;\r
-        $mess["message_cashier"] = $txt . " Спросите последние 4 цифры телефона который позвонит клиенту $user_balans";\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK ', FILE_APPEND);\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-//    public function actionSendMessage()\r
-//    {\r
-//        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-//        $data = file_get_contents('php://input');\r
-//        $result = json_decode($data, true);\r
-//\r
-//        $__API_PARAMS = ['store_id', 'seller_id', 'phone'];\r
-//\r
-//        foreach ($__API_PARAMS as $paramName) {\r
-//            if (empty($result[$paramName])) {\r
-//\r
-//                if ($paramName != 'phone') {\r
-//                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-//                }\r
-//\r
-//                return $this->asJson(["error" => "$paramName is required"]);\r
-//            }\r
-//        }\r
-//\r
-//        $phone = ClientHelper::phoneClear($result['phone']);\r
-//        if (!ClientHelper::phoneVerify($phone)) {\r
-//            return $this->asJson(["error_id" => 0.2, "error" => "phone is required"]);\r
-//        }\r
-//        $result['phone'] = $phone;\r
-//\r
-//        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->andWhere(['black_list' => '0'])->one();\r
-//        if (!$user) {\r
-//            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";\r
-//\r
-//            return $this->asJson($mess);\r
-//        }\r
-//        $keycode = $user->keycode;\r
-//\r
-//        $mess = [];\r
-//        $mess["result"] = true;\r
-//\r
-//        $mess['auth_code'] = $user->keycode;\r
-//        $mess['message_cashier'] = 'Отсканируйте QR код из телеграм бота или введите его руками';\r
-//\r
-//        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-//\r
-//        return $this->asJson($mess);\r
-//    }\r
-\r
-    public function actionSendMessage() {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $__API_PARAMS = ['store_id', 'seller_id', 'phone'];\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 0.2, "error" => "phone is required"]);\r
-        }\r
-        $result['phone'] = $phone;\r
-\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->andWhere(['black_list' => '0'])->one();\r
-        if (!$user) {\r
-            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-\r
-        $mess["message_cashier"] = "Звонок-последние 4 цифры телефона";\r
-\r
-        $userAuthCallLog = UsersAuthCallLog::find()->select(['COUNT(*) as cnt'])->where(['phone' => $result['phone']])\r
-            ->andWhere(['store_id' => $result['store_id']])->andWhere(['>=', 'date', date('Y-m-d H:i:s', strtotime('-10 minutes'))])->one();\r
-\r
-        $cnt = $userAuthCallLog ? $userAuthCallLog->cnt : 1;\r
-\r
-        $keycode = '';\r
-\r
-        if ($cnt < 2) {\r
-            $body = @file_get_contents("https://sms.ru/code/call?phone=" . $result['phone'] . "&api_id=4DFE45F9-1897-79C0-6872-08F05D6B7FA4&ip=" . $_SERVER["REMOTE_ADDR"]);\r
-            $json_res = json_decode($body, true, 512, JSON_UNESCAPED_UNICODE);\r
-            if ($json_res["status"] == "OK") {\r
-                $keycode = $json_res["code"];\r
-                $user->keycode = '' . $keycode;\r
-                $user->password = ClientHelper::generatePassword(8);;\r
-                $user->save();\r
-                if ($user->getErrors()) {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 3.1415, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-                    return $this->asJson(["error_id" => 3.1415, "error" => $user->getErrors()]);\r
-                }\r
-                $mess["auth_code"] = $keycode;\r
-                $mess["message_cashier"] = "Попытка:$cnt Звонок клиенту! последние 4 цифры номера";\r
-            }\r
-        } elseif ($cnt > 2) {\r
-            $mess["message_cashier"] = "Попытка $cnt -извиняемся перед клиентом";\r
-        }\r
-        $name = "$keycode Попытка $cnt  " . $_SERVER["REMOTE_ADDR"];\r
-        $userAuthCallLog = new UsersAuthCallLog;\r
-        $userAuthCallLog->date = date('Y-m-d H:i:s');\r
-        $userAuthCallLog->store_id = $result['store_id'];\r
-        $userAuthCallLog->seller_id = $result['seller_id'];\r
-        $userAuthCallLog->phone = $result['phone'];\r
-        $userAuthCallLog->name = $name;\r
-        $userAuthCallLog->save();\r
-        if ($userAuthCallLog->getErrors()) {\r
-            LogService::apiErrorLog(json_encode(["error_id" => 4.15, "error" => $userAuthCallLog->getErrors()], JSON_UNESCAPED_UNICODE));\r
-            return $this->asJson(["error_id" => 4.15, "error" => $userAuthCallLog->getErrors()]);\r
-        }\r
-\r
-        Yii::info("keykod={$user->keycode} store_id={$result['store_id']} seller_id={$result['seller_id']} phone={$result['phone']} $name", self::LOG_CATEGORY_BONUS);\r
-\r
-        $mess["timeout"] = 15;\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-//        $mess["message_cashier"] = "Звонок-последние 4 цифры телефона";\r
-//\r
-//        $userAuthCallLog = UsersAuthCallLog::find()->select(['COUNT(*) as cnt'])->where(['phone' => $result['phone']])\r
-//            ->andWhere(['store_id' => $result['store_id']])->andWhere(['>=', 'date', date('Y-m-d H:i:s', strtotime('-10 minutes'))])->one();\r
-//\r
-//        $cnt = $userAuthCallLog ? $userAuthCallLog->cnt : 1;\r
-//\r
-//        if ($cnt < 2) {\r
-//            $body = @file_get_contents("https://sms.ru/code/call?phone=" . $result['phone'] . "&api_id=4DFE45F9-1897-79C0-6872-08F05D6B7FA4&ip=" . $_SERVER["REMOTE_ADDR"]);\r
-//            $json_res = json_decode($body, true, 512, JSON_UNESCAPED_UNICODE);\r
-//\r
-//            if ($json_res["status"] == "OK") {\r
-//                $keycode = $json_res["code"];\r
-//                $user->keycode = '' . $keycode;\r
-//                $user->password = ClientHelper::generatePassword(8);;\r
-//                $user->save();\r
-//\r
-//                if ($user->getErrors()) {\r
-//\r
-//                    LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-//\r
-//                    return $this->asJson(["error_id" => 3, "error" => $user->getErrors()]);\r
-//                }\r
-//\r
-//                $mess["auth_code"] = $keycode;\r
-//                $mess["message_cashier"] = "Попытка:$cnt Звонок клиенту! последние 4 цифры номера";\r
-//            }\r
-//        } else if ($cnt > 2) {\r
-//            $mess["message_cashier"] = "Попытка $cnt -извиняемся перед клиентом";\r
-//        }\r
-//\r
-//        $name = "$keycode Попытка $cnt  " . $_SERVER["REMOTE_ADDR"];\r
-//        $userAuthCallLog = new UsersAuthCallLog;\r
-//        $userAuthCallLog->date = date('Y-m-d H:i:s');\r
-//        $userAuthCallLog->store_id = $result['store_id'];\r
-//        $userAuthCallLog->seller_id = $result['seller_id'];\r
-//        $userAuthCallLog->phone = $result['phone'];\r
-//        $userAuthCallLog->name = $name;\r
-//        $userAuthCallLog->save();\r
-//        if ($userAuthCallLog->getErrors()) {\r
-//\r
-//            LogService::apiErrorLog(json_encode(["error_id" => 4, "error" => $userAuthCallLog->getErrors()], JSON_UNESCAPED_UNICODE));\r
-//\r
-//            return $this->asJson(["error_id" => 4, "error" => $userAuthCallLog->getErrors()]);\r
-//        }\r
-//\r
-\r
-    public function actionSaveClientInfo()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $fl = date('_Y_m_d__H_i_s_');\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . $result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-\r
-        $__API_PARAMS = ['store_id', 'seller_id', 'phone']; // first_name, second_name, sex, birth_day, referral_id, comment, events\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-        $result['phone'] = $phone;\r
-\r
-        $source = $result["source"] ?? 0;\r
-        $store_id = $result["store_id"];\r
-        $store_id_guid = $store_id;\r
-        $seller_id = $result["seller_id"];\r
-        $phone = $result["phone"];\r
-        $first_name = $result["first_name"] ?? "";\r
-        $second_name = $result["second_name"] ?? "";\r
-        $sex2 = $result["sex"] ?? "";\r
-        $birth_day = $result["birth_day"] ?? "";\r
-        $referral_id = $result["referral_id"] ?? null;\r
-        $comment = $result["comment"] ?? "";\r
-        $events = $result["events"] ?? [];\r
-        $sex = "man";\r
-        if ($sex2 == "male") {\r
-            $sex = "man";\r
-        }\r
-        if ($sex2 == "female") {\r
-            $sex = "women";\r
-        }\r
-//        if ($referral_phone == $phone) {\r
-//            $referral_phone = "";\r
-//        }\r
-\r
-        $mess = [];\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        /* @var $user Users */\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->andWhere(['black_list' => '0'])->one();\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($user) {\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $user->referral_id = $referral_id != $user->id ? $referral_id : null;\r
-            $user->pol = $sex;\r
-            $user->bdate = $birth_day;\r
-            $user->name = "$first_name $second_name";\r
-            $user->comment = $comment;\r
-            $user->password = ClientHelper::generatePassword(8);\r
-            $user->keycode = '' . rand(1000, 9999);\r
-            $user->source = $source == 2 ? 1 : 0;\r
-            $user->save(); // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            if ($user->getErrors()) {\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                LogService::apiErrorLog(json_encode(["error_id" => 2, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                return $this->asJson(["error_id" => 2, "error" => $user->getErrors()]);\r
-            }\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $userEventOld = UsersEvents::find()->where(['phone' => $phone])->orderBy(['date_add' => SORT_ASC])->one();\r
-            if ($userEventOld && $userEventOld->date_add < date('Y-m-d H:i:s', time() - 2 * 86400)) { // Дата добавление последнего события не старше двух дней\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $mess["result"] = true;\r
-                $mess["message_cashier"] = "Возможность внесения памятных дат ограничена";\r
-\r
-                LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-                return $this->asJson($mess);\r
-            }\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-            $nomer_event = 1;\r
-            $dates = [];\r
-\r
-            foreach ($events as $k => $mass) {\r
-                $date = $mass["date"] ?? '';\r
-                $event_id = intval($mass['event_id'] ?? 0);\r
-\r
-                $datea = explode("-", $date);\r
-                $date_end = date("Y", time() + self::$YEAR_PERIOD * 86400) . "-" . $datea[1] . "-" . $datea[2];\r
-                $userEvent2 = UsersEvents::find()->where(['phone' => $phone])->andWhere(['date_day' => $datea[2]])->andWhere(['date_month' => $datea[1]])->one();\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($userEvent2) {\r
-                    $userEvent2->delete();\r
-                }\r
-                $userEvent3 = new UsersEvents;\r
-                $userEvent3->number = $nomer_event;\r
-                $userEvent3->date = $date;\r
-                $userEvent3->tip_id = $event_id;\r
-                $userEvent3->phone = $phone;\r
-                $userEvent3->date_day = $datea[2];\r
-                $userEvent3->date_month = $datea[1];\r
-                $userEvent3->date_add = date('Y-m-d H:i:s');\r
-                $userEvent3->tip = strval('???');\r
-                $userEvent3->name = 'М';\r
-                $userEvent3->sex = 'm';\r
-                $userEvent3->date_edit = date("Y-m-d H:i:s");\r
-                $userEvent3->date_edit_info = date("Y-m-d H:i:s");\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $userEvent3->save(); // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.\r
-                if ($userEvent3->getErrors()) {\r
-                    file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $userEvent3->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                    return $this->asJson(["error_id" => 3, "error" => $userEvent3->getErrors()]);\r
-                }\r
-                $dates [] = $date;\r
-                $nomer_event++;\r
-            }\r
-        } else {\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $created_name = $seller_id;\r
-            $rand = rand(1000, 9999);\r
-            $name = "$first_name $second_name";\r
-            $pass = ClientHelper::generatePassword(8);\r
-            $product1 = Products1c::find()->select(['name'])->where(['tip' => 'admin'])->andWhere(['id' => $seller_id])->one();\r
-            $product2 = Products1c::find()->select(['name'])->where(['tip' => 'city_store'])->andWhere(['id' => $store_id])->one();\r
-\r
-            $created_name = $product1 ? $product1->name : '';\r
-            $created_store = $product2 ? $product2->name : '';\r
-\r
-            $store_id_new = ExportImportTable::find()->select(['entity_id'])->where(['entity' => 'city_store'])->andWhere(['export_id' => '1'])\r
-                ->andWhere(['export_val' => $store_id])->one();\r
-            $seller_id_new = ExportImportTable::find()->select(['entity_id'])->where(['entity' => 'admin'])->andWhere(['export_id' => '1'])\r
-                ->andWhere(['export_val' => $seller_id])->one();\r
-            if ($store_id_new) {\r
-                $store_id_int = $store_id_new->entity_id;\r
-            }\r
-            if ($seller_id_new) {\r
-                $seller_id_int = $seller_id_new->entity_id;\r
-            }\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            Users::deleteAll(['phone' => $phone, 'phone_true' => '0']);\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $user2 = new Users;\r
-            $user2->source = $source;\r
-            $user2->pol = $sex;\r
-            $user2->keycode = strval($rand);\r
-            $user2->phone = $phone;\r
-            $user2->name = $name;\r
-            $user2->name_name = $first_name;\r
-            $user2->name_last = $second_name;\r
-            $user2->password = $pass;\r
-            $user2->phone_true = strval(1);\r
-            $user2->bdate = $birth_day;\r
-            $user2->referral_id = $referral_id;\r
-            $user2->comment = $comment;\r
-            $user2->created_id = $seller_id_int ?? 0;\r
-            $user2->created_name = $created_name;\r
-            $user2->seller_id = strval($seller_id);\r
-            $user2->store_id = $store_id_guid;\r
-            $user2->created_store_id = $store_id_int ?? 0;\r
-            $user2->created_store = $created_store;\r
-            $user2->date = date('Y-m-d H:i:s');\r
-            $user2->sale_store_id = $store_id_int ?? 0;\r
-            $user2->sale_store = '';\r
-            $user2->sms_info = 1;\r
-            $user2->reklama_info = 1;\r
-            $user2->info = '';\r
-            $setka_id = 1;\r
-            $user2->setka_id = $setka_id;\r
-            $user2->card = "" . ($phone * 2 + 1608 + $setka_id); // генерируем номер карты который зависит от номера сетки + ДР Тимура\r
-            $user2->save();  // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            if ($user2->getErrors()) {\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                LogService::apiErrorLog(json_encode(["error_id" => 4, "error" => $user2->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                return $this->asJson(["error_id" => 4, "error" => $user2->getErrors()]);\r
-            }\r
-\r
-            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            if ($store_id == '56524cb1-4763-11ea-8cce-b42e991aff6c') {\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $admin_id = ClientHelper::getExportId($seller_id, "admin", 1);\r
-\r
-                $usersBonus = new UsersBonus;\r
-                $usersBonus->date = date('Y-m-d H:i:s');\r
-                $usersBonus->tip = 'plus';\r
-                $usersBonus->tip_sale = 'podarok';\r
-                $usersBonus->phone = $phone;\r
-                $usersBonus->name = "Приветственные бонусы посетителю сайта";\r
-                $usersBonus->store_id = $store_id_int ?? 0;\r
-                $usersBonus->site_id = 0;\r
-                $usersBonus->referal_id = 0;\r
-                $usersBonus->admin_id = $admin_id;\r
-                $usersBonus->price = 0;\r
-                $usersBonus->price_skidka = 0;\r
-                $usersBonus->bonus = 50;\r
-                $usersBonus->store_id_1c = $store_id;\r
-                $usersBonus->seller_id_1c = $seller_id;\r
-                $usersBonus->date_start = date('Y-m-d 08:00:00', strtotime('+1 day', strtotime($usersBonus->date)));\r
-                $usersBonus->date_end = date('Y-m-d H:i:s', strtotime('+1 week', strtotime($usersBonus->date_start)));\r
-                $usersBonus->save();\r
-                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($usersBonus->getErrors()) {\r
-                    file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 5, "error" => $usersBonus->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                    return $this->asJson(["error_id" => 5, "error" => $usersBonus->getErrors()]);\r
-                }\r
-            }\r
-        }\r
-\r
-        $mess["result"] = true;\r
-        $mess["message_cashier"] = "Данные клиента сохранены";\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK ', FILE_APPEND);\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-    public function actionSale()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-        $resultTest = $result;\r
-        $fl = date('_Y_m_d__H_i_s_');\r
-        $json=json_encode($resultTest,JSON_UNESCAPED_UNICODE);\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '_info.json', PHP_EOL . '--' . $result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '_info.json', ' '.date("d.m.Y H:i:s",time()).' JSON: '.$json.'  ', FILE_APPEND);\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . $result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        $__API_PARAMS = ['store_id', 'seller_id', 'phone', 'check_amount', 'check_id', 'check_name']; // items, auth_code, write_off_bonuses\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        foreach ($__API_PARAMS as $paramName) {\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-error-' . __LINE__, FILE_APPEND);\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-error-' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $result['phone'] = $phone;\r
-        $result['items'] = $result['items'] ?? [];\r
-\r
-        $store_id = $result["store_id"];\r
-        $seller_id = $result["seller_id"];\r
-        $check_amount = $result["check_amount"];\r
-        $check_id = $result["check_id"];\r
-        $check_name = $result["check_name"];\r
-        $lid_id = $result["lid_id"] ?? 0;\r
-        $auth_code = $result['auth_code'] ?? 0;\r
-        $write_off_bonuses = intval($result["write_off_bonuses"] ?? 0); // только при продаже\r
-\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();\r
-        $bonusLevels = BonusLevels::find()->where(['active' => 1])->indexBy('alias')->asArray()->all();\r
-        $bonusLevel = $user->bonus_level ?? "silver";\r
-        $cashback_rate = isset($bonusLevels[$bonusLevel]['cashback_rate'])\r
-            ? $bonusLevels[$bonusLevel]['cashback_rate'] / 100\r
-            : self::$FIRST_SALE_PROCENT;\r
-\r
-//        $referal_rate = isset($bonusLevels[$bonusLevel]['referal_rate'])\r
-//            ? $bonusLevels[$bonusLevel]['referal_rate'] / 100\r
-//            : self::$CREDIT_HIGH_PROCENT;\r
-\r
-        $bonus_rate = isset($bonusLevels[$bonusLevel]['bonus_rate'])\r
-            ? $bonusLevels[$bonusLevel]['bonus_rate'] / 100\r
-            : self::$FIRST_SALE_PROCENT;\r
-\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $amount_real = 0;\r
-        $items_arr_no = array_values(ArrayHelper::map(\r
-            UniversalCatalogItem::find()->where(['catalog_alias' => 'unused_nomenclature'])->all(), 'guid', 'guid'));\r
-        $items_arr_no_bonus_writeoffs = array_values(ArrayHelper::map(\r
-            UniversalCatalogItem::find()->where(['catalog_alias' => 'non_bonusable_goods'])->all(), 'guid', 'guid'));\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $summa_no = 0;\r
-        $summa_no_writeoffs = 0;\r
-        $amount_all = 0;\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        foreach ($result['items'] as $item) {\r
-            if (in_array($item["product_id"], $items_arr_no)) {\r
-                $summa_no = $summa_no + $item["price"] * $item["quantity"];\r
-            } else if (in_array($item["product_id"], $items_arr_no_bonus_writeoffs)) {\r
-                $summa_no_writeoffs = $summa_no_writeoffs + $item["price"] * $item["quantity"];\r
-            } else {\r
-                $amount_real = $amount_real + $item["price"] * $item["quantity"];\r
-            }\r
-            $amount_all = $amount_all + $item["price"] * $item["quantity"];\r
-        }\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        $cnt = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])->count());\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-       // $max_procent = $cnt == 0 ? self::$FIRST_SALE_PROCENT : ($cnt == 1 ? self::$SECOND_SALE_PROCENT : self::$MAX_PROCENT);\r
-        $max_procent = $bonus_rate;\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        // если списывается в попытке больше бонусов чем может списаться -\r
-        $percent = $phone == "79049031399" ? 0.9 : $max_procent;\r
-        $write_off_bonuses_theory = round($amount_real * $percent);\r
-        if ($write_off_bonuses > $write_off_bonuses_theory) {\r
-            $write_off_bonuses = $write_off_bonuses_theory;\r
-        }\r
-        $user_balans = ClientHelper::getBonusBalance($phone);\r
-        if ($user_balans < $write_off_bonuses) {\r
-            $write_off_bonuses = $user_balans;\r
-        }\r
-\r
-        // TO8-22: Промо-списание БЛАГО\r
-        // Проверяем: если у клиента есть промо-баланс >= 350, покупка >= 1700 и 350 > стандартного максимума — списываем промо\r
-        $usePromoWriteOff = false;\r
-        $promoWriteOffAmount = 350;\r
-        $promoMinCheckAmount = 1700;\r
-\r
-        $now = date('Y-m-d H:i:s');\r
-        $promoPlusSum = (float) UsersBonus::find()\r
-            ->where(['phone' => $phone, 'tip' => 'plus', 'tip_sale' => Promocode::TIP_SALE_PROMOBONUS])\r
-            ->andWhere(['<=', 'date_start', $now])\r
-            ->andWhere(['>=', 'date_end', $now])\r
-            ->sum('bonus');\r
-        $promoMinusSum = (float) UsersBonus::find()\r
-            ->where(['phone' => $phone, 'tip' => 'minus', 'tip_sale' => Promocode::TIP_SALE_PROMOBONUS])\r
-            ->sum('bonus');\r
-        $promoBalance = max(0, $promoPlusSum - $promoMinusSum);\r
-\r
-        if ($promoBalance >= $promoWriteOffAmount\r
-            && $amount_all >= $promoMinCheckAmount\r
-            && $promoWriteOffAmount > $write_off_bonuses_theory\r
-        ) {\r
-            $usePromoWriteOff = true;\r
-            $write_off_bonuses = $promoWriteOffAmount;\r
-        }\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        // сумма со скидкой\r
-        $summa_chek = $amount_all - $write_off_bonuses;\r
-        $baza_back = $amount_real + $summa_no_writeoffs - $write_off_bonuses;\r
-\r
-        $mess = [];\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        /** @var $user Users */\r
-        if (!$user) {\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-        // TO8-22: При промо-списании burn_balans не трогаем — списываются промо-бонусы, а не обычные\r
-        if (!$usePromoWriteOff) {\r
-            $user->burn_balans = max(0, $user->burn_balans - $write_off_bonuses);\r
-        }\r
-        // [balans - burn_balance, burn_balans] - показать клиенту что мы сожгли сжигаемый баланс\r
-\r
-// старая точка проверки кода\r
-//        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-//        if ($user->keycode != strval($auth_code)) {\r
-//\r
-//            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-auth_code not valid-' . __LINE__ . ' keycode ' .$user->keycode . '|  auth_code ' . strval($auth_code), FILE_APPEND);\r
-//            return $this->asJson(['error' => 'auth_code not valid']);\r
-//        }\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $user_id = $user->id;\r
-//        $keycode = $user->keycode;\r
-//        $name = $user->name;\r
-//        $referral_id = $user->referral_id;\r
-//        $sale_avg_price = $user->sale_avg_price;\r
-        $sale_price = $user->sale_price;\r
-        $sale_cnt = $user->sale_cnt;\r
-//        if ($referral_id == $user_id) {\r
-//            $referral_id = 0;\r
-//        }\r
-        $ip = $_SERVER['REMOTE_ADDR'];\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-\r
-        $store_id_1c = $store_id;\r
-        $site_id = 0;\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        // получаем внутренний ID продаца - сотрудника из таблицы admin\r
-        $admin_id = ClientHelper::getExportId($seller_id, "admin", 1);\r
-        // получаем внутренний ID продаца - сотрудника из таблицы admin\r
-        $store_id = ClientHelper::getExportId($store_id_1c, "city_store", 1);\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $writeOffAlready = false;\r
-        if (!empty($lid_id)) {\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $tipSaleForCheck = $usePromoWriteOff ? Promocode::TIP_SALE_PROMOBONUS : 'sale';\r
-            $writeOffAlready = UsersBonus::find()->where(['lid_id' => $lid_id, 'phone' => $phone, 'tip_sale' => $tipSaleForCheck, 'tip' => 'minus'])->one() != null;\r
-        }\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-        $user_balans_new = $user_balans;\r
-        if ($write_off_bonuses && !$writeOffAlready) {\r
-            // TO8-22: При промо-списании auth_code не требуется (списание автоматическое)\r
-            if (!$usePromoWriteOff) {\r
-                // Проверка кода только при стандартном списании\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($user->keycode != strval($auth_code)) {\r
-\r
-                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-auth_code not valid-' . __LINE__ . ' keycode ' .$user->keycode . '|  auth_code ' . strval($auth_code), FILE_APPEND);\r
-                    return $this->asJson(['error' => 'auth_code not valid']);\r
-                }\r
-            }\r
-\r
-            // TO8-22: При промо-списании обычный баланс не уменьшается — списываются промо-бонусы\r
-            $user_balans_new = $usePromoWriteOff ? $user_balans : ($user_balans - $write_off_bonuses);\r
-            $tipSaleForWriteOff = $usePromoWriteOff ? Promocode::TIP_SALE_PROMOBONUS : 'sale';\r
-            $name_b = $usePromoWriteOff\r
-                ? "Списание промо-бонусов БЛАГО по чеку $check_name"\r
-                : "Спиcание бонусов по чеку $check_name";\r
-            $usersBonus = new UsersBonus;\r
-            $usersBonus->date = date('Y-m-d H:i:s');\r
-            $usersBonus->tip = 'minus';\r
-            $usersBonus->tip_sale = $tipSaleForWriteOff;\r
-            $usersBonus->phone = $phone;\r
-            $usersBonus->name = $name_b;\r
-            $usersBonus->check_id = $check_id;\r
-            $usersBonus->store_id = $store_id;\r
-            $usersBonus->ip = $ip;\r
-            $usersBonus->site_id = $site_id; // ???\r
-            $usersBonus->referal_id = 0;// $referal_id;\r
-            $usersBonus->admin_id = $admin_id;\r
-            $usersBonus->price = $summa_chek;\r
-            $usersBonus->price_skidka = $write_off_bonuses;\r
-            $usersBonus->bonus = $write_off_bonuses;\r
-            $usersBonus->store_id_1c = $store_id_1c;\r
-            $usersBonus->seller_id_1c = $seller_id;\r
-            $usersBonus->user_id = $user_id;             // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-            $usersBonus->lid_id = $lid_id;               // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-            $usersBonus->date_start = $usersBonus->date; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-            $usersBonus->date_end = $usersBonus->date;   // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-            $usersBonus->date_dell = $usersBonus->date;  // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-            $usersBonus->save(); // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-            if ($user->first_minus_balance === null) {\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $user->first_minus_balance = $usersBonus->date;\r
-                $user->save();\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            }\r
-\r
-            if ($usersBonus->getErrors()) {\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                LogService::apiErrorLog(json_encode(["error_id" => 4, "error" => $usersBonus->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                return $this->asJson(["error_id" => 4, "error" => $usersBonus->getErrors()]);\r
-            }\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            Yii::info("MINUS write_off_bonuses={$write_off_bonuses}", self::LOG_CATEGORY_BONUS);\r
-        }\r
-        // TO8-22: При промо-списании кэшбек НЕ начисляется\r
-        if ($usePromoWriteOff) {\r
-            Yii::info("PROMO write_off={$write_off_bonuses}, no cashback for {$phone}", self::LOG_CATEGORY_BONUS);\r
-\r
-            // Обновляем поля пользователя (аналогично стандартному пути)\r
-            $sale_price += $check_amount;\r
-            $sale_avg_price = round($sale_price / ($sale_cnt + 1));\r
-\r
-            $user->keycode = "" . rand(1000, 9999);\r
-            $user->password = ClientHelper::generatePassword(8);\r
-            $user->date_last_sale = date('Y-m-d H:i:s');\r
-            $user->sale_cnt = $sale_cnt + 1;\r
-            if ($user->sale_cnt == 1) {\r
-                $user->date_first_sale = $user->date_last_sale;\r
-            }\r
-            $user->sale_store_id = $store_id;\r
-            $user->sale_price = $sale_price;\r
-            $user->sale_avg_price = $sale_avg_price;\r
-            $user->check_id_last_sale = $check_id;\r
-            if (!$user->date) {\r
-                $user->date = (new \DateTime('now', new \DateTimeZone('Europe/Moscow')))->format('Y-m-d H:i:sP');\r
-            }\r
-            $user->balans = ClientHelper::getBonusBalance($phone);\r
-            $user->save();\r
-\r
-            if ($user->getErrors()) {\r
-                LogService::apiErrorLog(json_encode(["error_id" => 6, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-                return $this->asJson(["error_id" => 6, "error" => $user->getErrors()]);\r
-            }\r
-\r
-            $this->updateUserBonusLevel($user, $sale_price, $check_id, $check_name);\r
-\r
-            $mess["write_off_bonuses"] = $write_off_bonuses;\r
-            $mess["summa_chek"] = $summa_chek;\r
-            $mess["bonus_back"] = 0;\r
-            $mess["user_balans"] = $user_balans_new;\r
-            $mess["user_balans_actual"] = $user->balans;\r
-            $mess["promo_writeoff"] = true;\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-\r
-        //начисляем кэшбек клиенту 10% от покупки - с базы за вычитом бонусов которые он списывает\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $userFound = Users::find()->where(['phone' => $result['phone']])->one();\r
-        /** @var $userFound Users */\r
-        $salesCount = -1; /* Из-за нулевого значения по умолчанию куча клиентов получило бонус 20% за покупку */\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($userFound && $userFound->telegram_created_at) {\r
-            $salesCount = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])\r
-                ->andWhere(['>=', 'date', $userFound->telegram_created_at])->count());\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        }\r
-        $credit_procent_index = $userFound && $userFound->source > 0 && $salesCount == 0 ? 1 : 0;\r
-\r
-\r
-        $back10 = $back20 = 0;\r
-        $back1 = $back = round($baza_back * $cashback_rate);\r
-        $nm = "Возврат с покупки " . (100 * $cashback_rate) . "% $check_name сумма чека $check_amount";\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $userBonus2 = UsersBonus::find()->where(['phone' => $phone])->andWhere(['check_id' => $check_id])->andWhere(['site_id' => $site_id])\r
-            ->andWhere(['store_id' => $store_id])->andWhere(['tip' => 'plus'])->andWhere(['bonus' => $back])->andWhere(['name' => $nm])->one();\r
-        if (!$userBonus2) {\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $user_balans_new += $back;\r
-\r
-            $userBonus2 = new UsersBonus;\r
-            $userBonus2->tip = 'plus';\r
-            $userBonus2->tip_sale = 'sale';\r
-            $userBonus2->date = date('Y-m-d H:i:s');\r
-            $userBonus2->date_start = date('Y-m-d H:i:s', strtotime('+1 day', time()));\r
-            $userBonus2->date_end = date('Y-m-d H:i:s', strtotime('+' . self::$YEAR_PERIOD . ' day', time()));\r
-            $userBonus2->phone = $phone;\r
-            $userBonus2->name = $nm;\r
-            $userBonus2->check_id = $check_id;\r
-            $userBonus2->store_id = $store_id;\r
-            $userBonus2->bonus = $back;\r
-            $userBonus2->ip = $ip;\r
-            $userBonus2->site_id = $site_id;\r
-            $userBonus2->referal_id = 0; // $referal_id;\r
-            $userBonus2->admin_id = $admin_id;\r
-            $userBonus2->price = $summa_chek;\r
-            $userBonus2->store_id_1c = $store_id_1c;\r
-            $userBonus2->seller_id_1c = $seller_id;\r
-            $userBonus2->user_id = $user_id;\r
-            $userBonus2->lid_id = $lid_id;\r
-            $userBonus2->price_skidka = 0;\r
-            $userBonus2->date_dell = $userBonus2->date_end;\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            $userBonus2->save();\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            if ($userBonus2->getErrors()) {\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                LogService::apiErrorLog(json_encode(["error_id" => 5, "error" => $userBonus2->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                return $this->asJson(["error_id" => 5, "error" => $userBonus2->getErrors()]);\r
-            } else {\r
-                $back10 = $back;\r
-            }\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            Yii::info("PLUS bonus={$back}", self::LOG_CATEGORY_BONUS);\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            if ($credit_procent_index) {\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $back = round($baza_back * self::$CREDIT_HIGH_PROCENT_PART20);\r
-                $nm = "Возврат с покупки " . (100 * self::$CREDIT_HIGH_PROCENT_PART20) . "% $check_name сумма чека $check_amount";\r
-\r
-                $user_balans_new += $back;\r
-\r
-                $userBonus2 = new UsersBonus();\r
-                $userBonus2->tip = 'plus';\r
-                $userBonus2->tip_sale = 'sale';\r
-                $userBonus2->date = date('Y-m-d H:i:s');\r
-                $userBonus2->date_start = date('Y-m-d H:i:s', strtotime('+1 day', time()));\r
-                $userBonus2->date_end = date('Y-m-d H:i:s', strtotime('+3 month', time()));\r
-                $userBonus2->phone = $phone;\r
-                $userBonus2->name = $nm;\r
-                $userBonus2->check_id = $check_id;\r
-                $userBonus2->store_id = $store_id;\r
-                $userBonus2->bonus = $back;\r
-                $userBonus2->ip = $ip;\r
-                $userBonus2->site_id = $site_id;\r
-                $userBonus2->referal_id = 0;\r
-                $userBonus2->admin_id = $admin_id;\r
-                $userBonus2->price = $summa_chek;\r
-                $userBonus2->store_id_1c = $store_id_1c;\r
-                $userBonus2->seller_id_1c = $seller_id;\r
-                $userBonus2->user_id = $user_id;\r
-                $userBonus2->lid_id = $lid_id;\r
-                $userBonus2->price_skidka = 0;\r
-                $userBonus2->date_dell = $userBonus2->date_end;\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $userBonus2->save();\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($userBonus2->getErrors()) {\r
-\r
-                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 5.2, "error" => $userBonus2->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                    return $this->asJson(["error_id" => 5.2, "error" => $userBonus2->getErrors()]);\r
-                } else {\r
-                    $back20 = $back;\r
-                }\r
-                if ($userFound->telegram_created_at == null) {\r
-                    $userFound->telegram_created_at = date("Y-m-d H:i:s");\r
-\r
-                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                    $userFound->save();\r
-                    if ($userFound->getErrors()) {\r
-\r
-                        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-\r
-                        LogService::apiErrorLog(json_encode(["error_id" => 5.3, "error" => $userFound->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-                        return $this->asJson(["error_id" => 5.3, "error" => $userFound->getErrors()]);\r
-                    }\r
-                }\r
-\r
-                $notifiableUser = new NotifiableUser;\r
-                $notifiableUser->phone = $phone;\r
-                $notifiableUser->type = "first_given_bonus";\r
-                $notifiableUser->data = "" . ($back1 + $back);\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                $notifiableUser->save();\r
-\r
-                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($notifiableUser->getErrors()) {\r
-\r
-                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                    return $this->asJson(["error_id" => 5.4, "error" => $notifiableUser->getErrors()]);\r
-                }\r
-            }\r
-        }\r
-\r
-//        /////// Добавляем бонусов рефералу\r
-//        if ($referal_id && $back) {\r
-//            $name = "Вознаграждение за приведенного друга";\r
-//            $referalBonus = UsersBonus::find()->where(['phone' => $phone])->andWhere(['referal_id' => $referal_id])->andWhere(['tip' => 'plus'])->one(); // phone = referal_id ???\r
-//            if (!$referalBonus) {\r
-//                $referalBonus = new UsersBonus;\r
-//                $referalBonus->tip = 'plus';\r
-//                $referalBonus->tip_sale = 'referal';\r
-//                $referalBonus->date = date('Y-m-d H:i:s');\r
-//                $referalBonus->date_start = date('Y-m-d H:i:s', strtotime('+1 day', time()));\r
-//                $referalBonus->date_end = date('Y-m-d H:i:s', strtotime('+' . self::$YEAR_PERIOD . ' day', time()));\r
-//                $referalBonus->phone = $phone;\r
-//                $referalBonus->name = $name;\r
-//                $referalBonus->check_id = $check_id;\r
-//                $referalBonus->store_id = $store_id;\r
-//                $referalBonus->bonus = $back;\r
-//                $referalBonus->ip = $ip;\r
-//                $referalBonus->site_id = $site_id; // ??? $user_id_referal\r
-//                $referalBonus->referal_id = $referal_id; // ???\r
-//                $referalBonus->admin_id = $admin_id;\r
-//                $referalBonus->price = $summa_chek;\r
-//                $referalBonus->store_id_1c = $store_id_1c;\r
-//                $referalBonus->seller_id_1c = $seller_id;\r
-//                $referalBonus->save();\r
-//                if ($referalBonus->getErrors()) {\r
-//                    return $this->asJson(["error_id" => 3, "error" => $referalBonus->getErrors()]);\r
-//                }\r
-//            }\r
-//        }\r
-\r
-        ///////\r
-//        $itogo = 0;\r
-//        foreach ($result["items"] as $k => $mass) {\r
-//            $seller_id_item = $mass["seller_id"];\r
-//            $product_id = $mass["product_id"];\r
-//            $price = $mass["price"];\r
-//            $quantity = $mass["quantity"];\r
-//            $sm = $price * $quantity;\r
-//            //$info .=" id=$product_id ($quantity шт. x $price руб.)  = $sm руб.,";\r
-//            $itogo += $sm;\r
-//\r
-//            //получаем внутренний ID товара\r
-//            $item_id = ClientHelper::get_export_id($product_id, "products",1);\r
-//\r
-//            //товары к продаже\r
-//            $salesItem = new SalesItems;\r
-//            $salesItem->date = date('Y-m-d H:i:s');\r
-//            $salesItem->phone = $phone;\r
-//            $salesItem->check_id = $check_id;\r
-//            $salesItem->store_id = $store_id;\r
-//            $salesItem->store_id_1c = $store_id_1c;\r
-//            $salesItem->seller_id = $seller_id_item;\r
-//            $salesItem->admin_id = $admin_id;\r
-//            $salesItem->id_1c = $product_id;\r
-//            $salesItem->item_id = $item_id;\r
-//            $salesItem->kol = $quantity;\r
-//            $salesItem->summa = $sm;\r
-//            $salesItem->referal_id = 0; // $referal_id;\r
-//            $salesItem->color_id = 0; // $color_id ???\r
-//            $salesItem->lid_id = $lid_id;\r
-//            $salesItem->complect_id = 0;                 // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//            $salesItem->name = '???';                    // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//            $salesItem->skidka = $mass['discount'] ?? 0; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//            $salesItem->vozvrat = 0;                     // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//            $salesItem->save();\r
-//            if ($salesItem->getErrors()) {\r
-//                return $this->asJson(["error_id" => 4, "error" => $salesItem->getErrors()]);\r
-//            }\r
-//        }\r
-        // sale_avg_price sale_price\r
-        $sale_price += $check_amount;\r
-        $sale_avg_price = round($sale_price / ($sale_cnt + 1));\r
-\r
-        $user->keycode = "" . rand(1000, 9999);\r
-        $user->password = ClientHelper::generatePassword(8);\r
-        $user->date_last_sale = date('Y-m-d H:i:s');\r
-        $user->sale_cnt = $sale_cnt + 1;\r
-        if ($user->sale_cnt == 1) {\r
-            $user->date_first_sale = $user->date_last_sale;\r
-        }\r
-        $user->sale_store_id = $store_id;\r
-        $user->sale_price = $sale_price;\r
-        $user->sale_avg_price = $sale_avg_price;\r
-        $user->check_id_last_sale = $check_id;\r
-        if (!$user->date) {\r
-            $user->date = (new DateTime('now', new DateTimeZone('Europe/Moscow')))->format('Y-m-d H:i:sP');\r
-        }\r
-        $user->balans = ClientHelper::getBonusBalance($phone);\r
-\r
-//        $user->email_old = "example@example.ru"; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->phone_old = "71111111111";        // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->check_id_forgot = "???";          // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->sid_forgot = "???";               // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->alerts_balans = "???";            // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->alerts_date = "???";              // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->alerts_reklama = "???";           // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $user->seller_id = "???";                // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $user->save();\r
-        if ($user->getErrors()) {\r
-\r
-\r
-            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            LogService::apiErrorLog(json_encode(["error_id" => 6, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-            Yii::info("BEFORE END errors=" . json_encode($user->getErrors(), JSON_UNESCAPED_UNICODE), self::LOG_CATEGORY_BONUS);\r
-\r
-            return $this->asJson(["error_id" => 6, "error" => $user->getErrors()]);\r
-        } else {\r
-            $this->updateUserBonusLevel($user, $sale_price, $check_id, $check_name);\r
-        }\r
-        Yii::info("BEFORE END", self::LOG_CATEGORY_BONUS);\r
-//        $itogo -= $write_off_bonuses;\r
-\r
-//        // продажа заносим в таблицу\r
-//        $sale = new Sales;\r
-//        $sale->date = date("Y-m-d H:i:s");\r
-//        $sale->phone = $phone;\r
-//        $sale->operation = 'Продажа';\r
-//        $sale->store_id = $store_id;\r
-//        $sale->admin_id = $admin_id;\r
-//        $sale->seller_id = $seller_id;\r
-//        $sale->store_id_1c = $store_id_1c;\r
-//        $sale->id = $check_id;\r
-//        $sale->number = $check_name;\r
-//        $sale->summ = $amount_all;\r
-//        $sale->skidka = $write_off_bonuses;\r
-//        $sale->status = "???";        // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->payments = "???";      // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->pay_arr = "???";       // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->sales_check = "???";   // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->order_id = "";         // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->terminal_id = "???";   // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->terminal = "???";      // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->kkm_id = "???";        // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->held = 0;              // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->date_up = $sale->date; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении\r
-//        $sale->save();\r
-//        if ($sale->getErrors()) {\r
-//            return $this->asJson(["error_id" => 6, "error" => $sale->getErrors()]);\r
-//        }\r
-\r
-        $mess["result"] = true;\r
-        $mess["message_cashier"] = "Бонусы списаны";\r
-        $mess["user_balans_old"] = $user_balans;\r
-        $mess["user_balans_new"] = $user_balans_new;\r
-        $mess["user_balans_actual"] = $user->balans;\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK ', FILE_APPEND);\r
-\r
-        $totalBonus = $back10 + $back20;\r
-\r
-        $input = [\r
-            'phone' => $phone,\r
-            'bonusCount' => $totalBonus,\r
-            'purchaseDate' => date("Y-m-d H:i:s"),\r
-            'orderId' => $check_id,\r
-        ];\r
-\r
-        $userBonusSendToTgLogs = new UserBonusSendToTgLogs;\r
-        $userBonusSendToTgLogs->input_hash = md5(Json::encode($input));\r
-        $userBonusSendToTgLogs->input = Json::encode($input);\r
-        $userBonusSendToTgLogs->check_id = $check_id;\r
-        $userBonusSendToTgLogs->phone = $phone;\r
-        $userBonusSendToTgLogs->bonusCount = $totalBonus;\r
-        $userBonusSendToTgLogs->status = 1;\r
-        $userBonusSendToTgLogs->date = date('Y-m-d H:I:s');\r
-        $userBonusSendToTgLogs->save();\r
-        if ($userBonusSendToTgLogs->getErrors()) {\r
-            LogService::apiErrorLog(json_encode(["error_id" => 100.001, "error" => $userBonusSendToTgLogs->getErrors()], JSON_UNESCAPED_UNICODE));\r
-        }\r
-        Yii::$app->queue->push(new SendBonusInfoToSiteJob($input));\r
-\r
-//        SiteService::notifySiteAboutBonuses($phone, $totalBonus, date("Y-m-d H:i:s"), $check_id);\r
-\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-    /**\r
-     * Создаёт новую запись в таблице UsersBonusLevels.\r
-     *\r
-     * @param Users $user\r
-     * @param string $bonusLevel\r
-     * @param string $check_id\r
-     * @param string $check_name\r
-     * @param string $createdAt\r
-     * @return bool\r
-     */\r
-    protected function createBonusHistoryRecord($user, $bonusLevel, $check_id, $check_name, $createdAt)\r
-    {\r
-        $bonusRecord = new UsersBonusLevels();\r
-        $bonusRecord->phone = $user->phone;\r
-        $bonusRecord->user_id = $user->id;\r
-        $bonusRecord->bonus_level = $bonusLevel;\r
-        $bonusRecord->date_from = $createdAt;\r
-        $bonusRecord->check_id = $check_id;\r
-        $bonusRecord->check_name = $check_name;\r
-        $bonusRecord->active = 1;\r
-\r
-        if (!$bonusRecord->save()) {\r
-            LogService::apiErrorLog(\r
-                json_encode(["error_id" => 100, "error" => $bonusRecord->getErrors()], JSON_UNESCAPED_UNICODE)\r
-            );\r
-            return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Обновляет бонусный уровень пользователя.\r
-     *\r
-     * @param Users  $user       Модель пользователя.\r
-     * @param float  $sale_price Текущая сумма покупок.\r
-     * @param string $check_id   Идентификатор чека.\r
-     * @param string $check_name Имя (номер) чека.\r
-     */\r
-    protected function updateUserBonusLevel($user, $sale_price, $check_id, $check_name)\r
-    {\r
-        $bonusLevels = BonusLevels::find()\r
-            ->where(['active' => 1])\r
-            ->orderBy(['threshold' => SORT_ASC])\r
-            ->all();\r
-\r
-        $computedBonusLevel = null;\r
-        foreach ($bonusLevels as $level) {\r
-            if ($sale_price > $level->threshold) {\r
-                $computedBonusLevel = $level->alias;\r
-            }\r
-        }\r
-        $newBonusLevel = $computedBonusLevel ?? 'silver';\r
-\r
-        $existingHistoryLevel = UsersBonusLevels::find()\r
-            ->where(['or', ['phone' => $user->phone], ['user_id' => $user->id]])\r
-            ->andWhere(['active' => 1])\r
-            ->one();\r
-\r
-        $now = date('Y-m-d H:i:s');\r
-\r
-        if (empty($user->bonus_level) || $user->bonus_level !== $newBonusLevel) {\r
-            $user->bonus_level = $newBonusLevel;\r
-            if (!$user->save()) {\r
-                LogService::apiErrorLog(\r
-                    json_encode(["error_id" => 6.1, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE)\r
-                );\r
-            }\r
-\r
-            if ($existingHistoryLevel) {\r
-                $existingHistoryLevel->active = 0;\r
-                $existingHistoryLevel->date_to = $now;\r
-                if (!$existingHistoryLevel->save()) {\r
-                    LogService::apiErrorLog(\r
-                        json_encode(\r
-                            ["error_id" => 6.2, "error" => $existingHistoryLevel->getErrors()],\r
-                            JSON_UNESCAPED_UNICODE\r
-                        )\r
-                    );\r
-                }\r
-            }\r
-\r
-            $this->createBonusHistoryRecord($user, $newBonusLevel, $check_id, $check_name, $now);\r
-        }\r
-    }\r
-\r
-    public function actionGetClientInfo()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $__API_PARAMS = ['phone'];\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-\r
-        $mess = [];\r
-        $user = Users::find()->select(['id', 'keycode', 'bonus_level', 'burn_balans', 'name', 'referral_id', 'bdate', 'comment', 'pol', 'extract(epoch FROM  date) as date'])\r
-            ->where(['phone' => $phone])->one();\r
-        if (!$user) {\r
-            $mess["error"] = "Покупателя " . $phone . " нет в бонусной программе!";\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-        $name = explode(" ", $user->name);\r
-        $birth_day = $user->bdate;\r
-        $first_name = $name[0] ?? '';\r
-        $second_name = $name[1] ?? '';\r
-        $comment = $user->comment;\r
-        $pol = "male";\r
-        if ($user->pol == "women") {\r
-            $pol = "female";\r
-        }\r
-        // если с момента добавления клиента прошло не более 5 часов позволяем редактировать даты иначе запрещаем редактирование\r
-        if ($user->date > time() - 3600 * 5) {\r
-            $mess["birth_day_readonly"] = true;\r
-            $mess["events_readonly"] = false;\r
-        }\r
-        if ($birth_day) {\r
-            $mess["birth_day_readonly"] = true;\r
-        }\r
-\r
-        $data = UsersEvents::find()->where(['phone' => $phone])->orderBy(['date' => SORT_DESC])->all();\r
-        foreach ($data as $row) {\r
-            if (strlen($row->date_day) == 1) {\r
-                $row->date_day = "0" . $row->date_day;\r
-            }\r
-            if (strlen($row->date_month) == 1) {\r
-                $row->date_month = "0" . $row->date_month;\r
-            }\r
-            if (!isset($mess["events"])) {\r
-                $mess["events"] = [];\r
-            }\r
-            $mess["events"][] = ["date" => $row->date, "event_id" => $row->tip_id];\r
-        }\r
-\r
-        $user_balance = ClientHelper::getBonusBalance($phone);\r
-\r
-        $mess["result"] = true;\r
-        $mess["sex"] = $pol;\r
-        $mess["first_name"] = $first_name;\r
-        $mess["second_name"] = $second_name;\r
-        $mess["birth_day"] = $birth_day;\r
-        $mess["comment"] = $comment;\r
-        $mess["balance"] = $user_balance;\r
-        $mess["bonus_level"] = $user->bonus_level;\r
-        $mess["burn_balans"] = $user->burn_balans;\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-    public function actionReturn()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $__API_PARAMS = ['store_id', 'check_id']; // check_name, seller_id\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (!isset($result[$paramName])) {\r
-\r
-                LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        $store_id = $result["store_id"];\r
-//        $seller_id = $result["seller_id"] ?? '';\r
-        $check_id = $result["check_id"];\r
-//        $check_name = $result["check_name"] ?? '';\r
-\r
-        UsersBonus::deleteAll(['and', ['check_id' => $check_id],\r
-            ['>', 'date', date('Y-m-d H:i:s', strtotime('-3 day', time()))]]);\r
-\r
-        // api_logs... event: return, seller_id, when, check\r
-\r
-        LogService::apiLogs(1, json_encode(['Удачный возврат'], JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson(['ok']);\r
-    }\r
-\r
-    public function actionAuthCodeFail()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $__API_PARAMS = [/*'store_id', 'seller_id',*/\r
-            'phone'];\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-        $result['phone'] = $phone;\r
-\r
-//        $seller_id = $result['seller_id'];\r
-//        $store_id = $result['store_id'];\r
-\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();\r
-        if (!$user) {\r
-            $mess["error"] = "Покупателя $phone нет в бонусной программе!";\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-\r
-        $user->keycode = "" . rand(1000, 9999);\r
-        $user->save();\r
-\r
-        if ($user->getErrors()) {\r
-\r
-            LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));\r
-\r
-            return $this->asJson(["error_id" => 3, "error" => $user->getErrors()]);\r
-        }\r
-\r
-        $mess = [];\r
-        $mess["result"] = true;\r
-        // api_logs seller, store\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-    public function actionCurrentItems()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $__API_PARAMS = ['store_id', 'seller_id', 'phone', 'amount_no_discount', 'amount_to_pay'];\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (empty($result[$paramName])) {\r
-\r
-                if ($paramName != 'phone') {\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-        $result['phone'] = $phone;\r
-\r
-//        $store_id = $result["store_id"];\r
-//        $seller_id = $result["seller_id"];\r
-        $phone = $result["phone"];\r
-//        $check_amount = intval($result["check_amount"] ?? 0);\r
-//        $check_id = $result["check_id"] ?? '';\r
-//        $check_name = $result["check_name"] ?? '';\r
-//        $items = $result['items'] ?? [];\r
-\r
-        $mess = [];\r
-\r
-        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();\r
-        if (!$user) {\r
-            $mess["error"] = "Покупателя $phone нет в бонусной программе!";\r
-\r
-            return $this->asJson($mess);\r
-        }\r
-\r
-//        $user_balans = ClientHelper::getBonusBalance($phone);\r
-//        $max = $itogo * self::$MAX_PROCENT;  // максимально можем разрешить списывать до 30 процентов от суммы заказа\r
-//        $max = ceil($max);\r
-//        $available_bonus = $user_balans;\r
-//        if ($available_bonus > $max) { // если баллов бонусов больше чем 30 процентов списываем по максимуму 30\r
-//            $available_bonus = $max;\r
-//        }\r
-//        $baza = $check_amount - $available_bonus;\r
-//        $back = ceil(self::$CREDIT_PROCENT * $baza);\r
-\r
-\r
-//        foreach ($items as $k => $mass) {\r
-//            $seller_id_item = $mass["seller_id"];\r
-//            $product_id = $mass["product_id"];\r
-//            $price = $mass["price"];\r
-//            $quantity = $mass["quantity"];\r
-//            $sm = $price * $quantity;\r
-//            $info .=" id=$product_id ($quantity шт. x $price руб.)  = $sm руб.,";\r
-//            $itogo += $sm;\r
-//\r
-//            //получаем внутренний ID товара\r
-//            $item_id = ClientHelper::getExportId($product_id, "products",1);\r
-//        }\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson(["NOT IMPLEMENTED"]);\r
-    }\r
-\r
-    public function actionGetSettings()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        $__API_PARAMS = ['store_id', 'seller_id'];\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (!isset($result[$paramName])) {\r
-\r
-                LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-\r
-        $store_id = $result['store_id'];\r
-        $seller_id = $result['seller_id'];\r
-\r
-        $mess = [];\r
-        $mess["result"] = true;\r
-        $mess["attempts_auth_code"] = 5; // Количество возможных попыток ввода кода подтверждения\r
-        $mess["list_events"] = [\r
-            ["id" => 1, "name" => "День рождения"],\r
-            ["id" => 2, "name" => "8 марта"],\r
-            ["id" => 3, "name" => "День матери"],\r
-            ["id" => 4, "name" => "День влюбленных"],\r
-            ["id" => 5, "name" => "День свадьбы"],\r
-            ["id" => 6, "name" => "Другое"]\r
-        ];\r
-        $mess["send_current_items"] = false; // Отправлять или нет текущие позиции чека\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson($mess);\r
-    }\r
-\r
-    public function actionGetUnusedNumenclatur() {\r
-        $items_arr_no = array_values(ArrayHelper::map(\r
-            UniversalCatalogItem::find()->where(['catalog_alias' => 'unused_nomenclature'])->all(), 'guid', 'guid'));\r
-        return $this->asJson(['unused_nomenclature' => $items_arr_no]);\r
-    }\r
-\r
-    public function actionGetContest001Participant() {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-\r
-        $request = Yii::$app->request->getRawBody();\r
-\r
-        try {\r
-            $result = Json::decode($request);\r
-        } catch (\Exception $ex) {\r
-            return $this->asJson(['error' => ['code' => 400, 'message' => 'Json body invalid']]);\r
-        }\r
-\r
-        if (!isset($result['phone'])) {\r
-            return $this->asJson(["error_id" => 1, "error" => "phone is required"]);\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-        $result['phone'] = $phone;\r
-\r
-        $mess = [];\r
-\r
-        $contestants = Contest001::find()->where(['phone' => $phone])->all();\r
-        if (count($contestants) > 0) {\r
-            $mess['is_participant'] = true;\r
-            $raffle_numbers = [];\r
-            foreach ($contestants as $contestant) {\r
-                $raffle_numbers[] = $contestant->number;\r
-            }\r
-            $mess['raffle_numbers'] = implode(', ', $raffle_numbers);\r
-        } else {\r
-            $mess['is_participant'] = false;\r
-        }\r
-\r
-        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));\r
-\r
-        return $this->asJson(['response' => $mess]);\r
-    }\r
-\r
-    public function actionAdd() {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-\r
-        $request = Yii::$app->request->getRawBody();\r
-\r
-        $requestTest = json_decode(\Yii::$app->getRequest()->getRawBody(), true);\r
-\r
-        $fl = date('_Y_m_d__H_i_s_');\r
-\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', implode(',', $requestTest));\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        try {\r
-            $result = Json::decode($request);\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        } catch (\Exception $ex) {\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(['error' => ['code' => 400, 'message' => 'Json body invalid']]);\r
-        }\r
-\r
-        $__API_PARAMS = ['phone', 'description', 'tip_sale', 'bonus', 'date_end'];\r
-\r
-        foreach ($__API_PARAMS as $paramName) {\r
-            if (!isset($result[$paramName])) {\r
-                file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                if ($paramName != 'phone') {\r
-                    file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));\r
-                }\r
-                file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);\r
-            }\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        //\r
-        if (!in_array($result['tip_sale'], ['podarok', 'senat', 'nino802', 'sale', '14feb', '23feb', '8mar', 'quest001'])) {\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(["error_id" => 1.1, "error" => "tip_sale не разрешён (podarok, senat, nino802)"]);\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);\r
-        }\r
-        $result['phone'] = $phone;\r
-\r
-        $stop = UsersStopList::find()->select(['phone'])->where(['phone' => $result['phone']])->one();\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($stop) {\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(["error_id" => 4, "error" => 'Номер телефона числится в стоп листе']);\r
-        }\r
-\r
-        $bonus = min((int)$result['bonus'], 1000);\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $found = UsersBonus::find()->where(['phone' => $phone])->andWhere(['>=', 'date_start', date('Y-m-d H:i:s', time() - self::$YEAR_PERIOD * 86400)])\r
-            ->andWhere(['tip_sale' => $result['tip_sale']])->andWhere(['tip' => 'plus'])->andWhere(['bonus' => $bonus])->one();\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($found) {\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            return $this->asJson(["error_id" => 3, "error" => 'Бонусы уже начисляли']);\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        $userBonus = new UsersBonus;\r
-        $userBonus->phone = $phone;\r
-        $userBonus->name = $result['description'];\r
-        $userBonus->date = date('Y-m-d H:i:s');\r
-        $userBonus->site_id = 1;\r
-        $userBonus->setka_id = 1;\r
-        $userBonus->tip = 'plus';\r
-        $userBonus->tip_sale = $result['tip_sale'];\r
-        $userBonus->bonus = $bonus;\r
-\r
-        $userBonusDateStart = $result['date_start'] ?? $userBonus->date;\r
-        $userBonus->date_start = date('Y-m-d H:i:s', strtotime($userBonusDateStart));\r
-\r
-        $userBonusDateEnd = $result['date_end'];\r
-        $userBonus->date_end = date('Y-m-d H:i:s', strtotime($userBonusDateEnd));\r
-\r
-        $userBonus->save();\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-        if ($userBonus->getErrors()) {\r
-            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);\r
-            LogService::apiErrorLog(json_encode(["error_id" => 2, "error" => $userBonus->getErrors()], JSON_UNESCAPED_UNICODE));\r
-            return $this->asJson(["error_id" => 2, "error" => $userBonus->getErrors()]);\r
-        }\r
-        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK', FILE_APPEND);\r
-        return $this->asJson(['response' => true]);\r
-    }\r
-\r
-    /**\r
-     * TO8-22: Активация промокода БЛАГО.\r
-     * POST /bonus/activate-promocode\r
-     * Параметры: phone, code\r
-     *\r
-     * Логика:\r
-     * 1. Найти промокод по коду\r
-     * 2. Проверить isActivatable()\r
-     * 3. Найти клиента по телефону\r
-     * 4. В транзакции: начислить 350 промо-бонусов (tip_sale='promobonus'), пометить промокод used=1\r
-     */\r
-    public function actionActivatePromocode()\r
-    {\r
-        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;\r
-        $data = file_get_contents('php://input');\r
-        $result = json_decode($data, true);\r
-\r
-        if (empty($result['code'])) {\r
-            return $this->asJson(["error_id" => 1, "error" => "code is required"]);\r
-        }\r
-        if (empty($result['phone'])) {\r
-            return $this->asJson(["error_id" => 4, "error" => "phone is required"]);\r
-        }\r
-\r
-        $phone = ClientHelper::phoneClear($result['phone']);\r
-        if (!ClientHelper::phoneVerify($phone)) {\r
-            return $this->asJson(["error_id" => 4, "error" => "phone is not valid"]);\r
-        }\r
-\r
-        $user = Users::find()\r
-            ->where(['phone' => $phone])\r
-            ->andWhere(['phone_true' => '1'])\r
-            ->one();\r
-\r
-        if (!$user) {\r
-            return $this->asJson(["error_id" => 4, "error" => "Пользователь с телефоном $phone не найден"]);\r
-        }\r
-\r
-        $transaction = Yii::$app->db->beginTransaction();\r
-        try {\r
-            // SELECT FOR UPDATE — блокируем строку промокода от параллельной активации\r
-            $promocode = Promocode::find()\r
-                ->where(['code' => $result['code']])\r
-                ->forUpdate()\r
-                ->one();\r
-\r
-            if (!$promocode) {\r
-                $transaction->rollBack();\r
-                return $this->asJson(["error_id" => 1, "error" => "Промокод не найден"]);\r
-            }\r
-\r
-            $activatable = $promocode->isActivatable();\r
-            if ($activatable !== true) {\r
-                $transaction->rollBack();\r
-                $errorMessages = [\r
-                    1 => "Промокод неактивен",\r
-                    2 => "Промокод уже использован",\r
-                    3 => "Срок действия промокода истёк",\r
-                ];\r
-                return $this->asJson([\r
-                    "error_id" => $activatable,\r
-                    "error" => $errorMessages[$activatable] ?? "Промокод недоступен",\r
-                ]);\r
-            }\r
-\r
-            $bonusAmount = $promocode->bonus ?: 350;\r
-            $duration = $promocode->duration ?: self::$YEAR_PERIOD;\r
-\r
-            $usersBonus = new UsersBonus();\r
-            $usersBonus->phone = $phone;\r
-            $usersBonus->name = "Активация промокода {$promocode->code}";\r
-            $usersBonus->date = date('Y-m-d H:i:s');\r
-            $usersBonus->tip = 'plus';\r
-            $usersBonus->tip_sale = Promocode::TIP_SALE_PROMOBONUS;\r
-            $usersBonus->bonus = $bonusAmount;\r
-            $usersBonus->price = 0;\r
-            $usersBonus->price_skidka = 0;\r
-            $usersBonus->user_id = $user->id;\r
-            $usersBonus->store_id = 0;\r
-            $usersBonus->site_id = 0;\r
-            $usersBonus->setka_id = 0;\r
-            $usersBonus->referal_id = 0;\r
-            $usersBonus->admin_id = 0;\r
-            $usersBonus->lid_id = 0;\r
-            $usersBonus->date_start = date('Y-m-d H:i:s');\r
-            $usersBonus->date_end = date('Y-m-d H:i:s', strtotime('+' . $duration . ' day'));\r
-            $usersBonus->date_dell = $usersBonus->date_end;\r
-            $usersBonus->ip = $_SERVER['REMOTE_ADDR'] ?? '';\r
-\r
-            if (!$usersBonus->save()) {\r
-                throw new \Exception('Ошибка сохранения бонуса: ' . json_encode($usersBonus->getErrors()));\r
-            }\r
-\r
-            $promocode->used = Promocode::USED_YES;\r
-            $promocode->activated_by = $user->id;\r
-            $promocode->activated_at = date('Y-m-d H:i:s');\r
-            if (!$promocode->save(false)) {\r
-                throw new \Exception('Ошибка обновления промокода: ' . json_encode($promocode->getErrors()));\r
-            }\r
-\r
-            $transaction->commit();\r
-\r
-            Yii::info("Промокод {$promocode->code} активирован для {$phone}, бонус={$bonusAmount}", self::LOG_CATEGORY_BONUS);\r
-\r
-            return $this->asJson([\r
-                "success" => true,\r
-                "bonus" => $bonusAmount,\r
-                "message" => "Промокод активирован, начислено {$bonusAmount} бонусов",\r
-            ]);\r
-        } catch (\Exception $e) {\r
-            $transaction->rollBack();\r
-            LogService::apiErrorLog(json_encode([\r
-                "error_id" => 10,\r
-                "error" => $e->getMessage(),\r
-                "phone" => $phone,\r
-                "code" => $result['code'],\r
-            ], JSON_UNESCAPED_UNICODE));\r
-            return $this->asJson(["error_id" => 10, "error" => "Ошибка активации промокода"]);\r
-        }\r
-    }\r
-}\r
+<?php
+
+namespace app\controllers;
+
+use yii_app\jobs\SendBonusInfoToSiteJob;
+use DateTime;
+use DateTimeZone;
+use Yii;
+use yii\helpers\ArrayHelper;
+use yii\helpers\Json;
+use yii_app\helpers\ClientHelper;
+use yii_app\records\BonusLevels;
+use yii_app\records\Contest001;
+use yii_app\records\ExportImportTable;
+use yii_app\records\MessagerUser;
+use yii_app\records\NotifiableUser;
+use yii_app\records\Products1c;
+use yii_app\records\Sales;
+use yii_app\records\Timetable;
+use yii_app\records\UniversalCatalogItem;
+use yii_app\records\UserBonusSendToTgLogs;
+use yii_app\records\Users;
+use yii_app\records\UsersAuthCallLog;
+use yii_app\records\UsersBonus;
+use yii_app\records\UsersBonusLevels;
+use yii_app\records\UsersEvents;
+use yii_app\records\UsersPhones;
+use yii_app\records\UsersStopList;
+use yii_app\records\Promocode;
+use yii_app\services\LogService;
+use yii_app\services\SiteService;
+
+class BonusController extends BaseController
+{
+    private const LOG_CATEGORY_BONUS = 'bonus.auth';
+
+    private static $YEAR_PERIOD = 366;
+    private static $FIRST_SALE_PROCENT = 0.1;
+    private static $SECOND_SALE_PROCENT = 0.15;
+    private static $MAX_PROCENT = 0.2;
+    private static $CREDIT_PROCENT = 0.1;
+    private static $CREDIT_HIGH_PROCENT = 0.3;
+    private static $CREDIT_HIGH_PROCENT_PART20 = 0.2;
+
+    const OUT_DIR =
+//        "/tmp";
+        "/var/www/erp24/api2/json"; // "/www/api2/json";
+// __DIR__ . "/../json"; //local
+
+    public function actionGetBonuses()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $fl = date('_Y_m_d__H_i_s_');
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . $result['phone']);
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        $__API_PARAMS = ['store_id', 'seller_id', 'phone']; // check_amount, items
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    LogService::apiErrorLog(json_encode(["error_id" => 0, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error_id" => 0, "error" => "$paramName is required"]);
+            }
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 0.2, "error" => "phone is required"]);
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $result['phone'] = $phone;
+
+        $check_amount = intval($result['check_amount'] ?? 0);
+
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();
+        $bonusLevels = BonusLevels::find()->where(['active' => 1])->indexBy('alias')->asArray()->all();
+        $bonusLevel = $user->bonus_level ?? "silver";
+
+        $bonus_rate = isset($bonusLevels[$bonusLevel]['bonus_rate'])
+            ? $bonusLevels[$bonusLevel]['bonus_rate'] / 100
+            : self::$FIRST_SALE_PROCENT;
+
+        $cashback_rate = isset($bonusLevels[$bonusLevel]['cashback_rate'])
+            ? $bonusLevels[$bonusLevel]['cashback_rate'] / 100
+            : self::$FIRST_SALE_PROCENT;
+
+        $mess = [];
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        // массив с id товарыми не участвующих в бонусной
+        $items_arr_no = array_values(ArrayHelper::map(
+            UniversalCatalogItem::find()->where(['catalog_alias' => 'unused_nomenclature'])->all(), 'guid', 'guid'));
+        $items_arr_no_bonus_writeoffs = array_values(ArrayHelper::map(
+            UniversalCatalogItem::find()->where(['catalog_alias' => 'non_bonusable_goods'])->all(), 'guid', 'guid'));
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        $all_amount = 0;
+        $has_actions = false;
+        $summa_no = 0;
+        $summa_no_writeoffs = 0;
+        if (!empty($result["items"])) {
+            foreach ($result["items"] as $item) {
+                if (in_array($item["product_id"], $items_arr_no)) {
+                    $summa_no = $summa_no + $item["price"] * $item["quantity"];
+                    $has_actions = true;
+                } elseif (in_array($item["product_id"], $items_arr_no_bonus_writeoffs)) {
+                    $summa_no_writeoffs = $summa_no_writeoffs + $item["price"] * $item["quantity"];
+                }
+                $all_amount += $item["price"] * $item["quantity"];
+            }
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $baza_nachislenie = $all_amount - $summa_no;
+
+        $check_amount = $check_amount - $summa_no;
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        //$cnt = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])->count());
+        //$max_procent = $cnt == 0 ? self::$FIRST_SALE_PROCENT : ($cnt == 1 ? self::$SECOND_SALE_PROCENT : self::$MAX_PROCENT);
+        $max_procent = $bonus_rate;
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $percent = ($result['phone'] == "79049031399") ? 0.9 : $max_procent;
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $userFound = Users::find()->where(['phone' => $result['phone']])->one();
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        /** @var $userFound Users */
+        $salesCount = -1; /* Из-за нулевого значения по умолчанию куча клиентов получило бонус 20% за покупку */
+        if ($userFound && $userFound->telegram_created_at) {
+            $salesCount = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])
+                ->andWhere(['>=', 'date', $userFound->telegram_created_at])->count());
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $credit_procent = $userFound && $userFound->source > 0 && $salesCount == 0 ? self::$CREDIT_HIGH_PROCENT : $cashback_rate;
+        $will_be_credited_bonuses = $credit_procent * $baza_nachislenie;
+
+        $will_be_credited_bonuses = round($will_be_credited_bonuses);
+        $mess["will_be_credited_bonuses"] = $will_be_credited_bonuses;
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $store_id = ClientHelper::getExportId($result['store_id'], "city_store", 1);
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        // Логи введённых номеров телефонов кассирами
+        $userPhone = UsersPhones::find()->where(['phone' => $result['phone']])->andWhere(['store_id' => $store_id])
+            ->andWhere(['seller_id' => $result['seller_id']])->one();
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!$userPhone) {
+            $userPhone = new UsersPhones();
+            $userPhone->phone = $result['phone'];
+            $userPhone->store_id = $store_id;
+            $userPhone->store_guid = $result['store_id'];
+            $userPhone->seller_id = $result['seller_id'];
+        }
+        if (!$userPhone->store_guid) {
+            $userPhone->store_guid = $result['store_id'];
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $userPhone->date = date('Y-m-d H:i:s');
+        $userPhone->save();
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($userPhone->getErrors()) {
+            file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => $userPhone->getErrors()], JSON_UNESCAPED_UNICODE));
+
+            return $this->asJson(["error_id" => 1, "error" => $userPhone->getErrors()]);
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!$user) {
+            file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '-нет в бонусной программе-' . __LINE__, FILE_APPEND);
+            $mess["new_client"] = true;
+            $mess["message_cashier"] = "Заполните данные клиента";
+            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";
+
+            return $this->asJson($mess);
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $keycode = $user->keycode;
+        $black_list = $user->black_list;
+
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!$black_list) {
+            file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $stop = UsersStopList::find()->select(['phone'])->where(['phone' => $result['phone']])->one();
+            if ($stop) {
+                $black_list = 1;
+                $user->black_list = 1;
+                file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $user->save();
+                file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($user->getErrors()) {
+                    file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                    LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                    return $this->asJson(["error_id" => 3, "error" => $user->getErrors()]);
+                }
+            }
+        }
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $name = $user->name;
+        $user_balans = ClientHelper::getBonusBalance($result['phone']);
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $baza_spisanie = $baza_nachislenie - $summa_no_writeoffs;
+        if ($baza_spisanie < 0) {
+            $baza_spisanie = 0;
+        }
+        $max = $baza_spisanie * $percent;  // максимально можем разрешить списывать до 30 процентов от суммы заказа
+        $max = round($max);
+        $available_bonus = $user_balans;
+        if ($available_bonus > $max) { // если баллов бонусов больше чем 30 процентов списываем по максимуму 30
+            $available_bonus = $max;
+        }
+//        $baza = $check_amount - $bonus;
+
+        $mess["message_cashier"] = "Клиент $name найден"; // Код: $keycode
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($black_list) {
+            $mess['error'] = 'Этот номер в черном списке';
+
+            return $this->asJson($mess);
+        }
+
+        $txt = $has_actions ? 'В чеке есть акционные товары, на них бонусы не начислятся.' : '';
+
+        $mess["result"] = true;
+        $mess["auth_code"] = $keycode;
+        $mess["name"] = $name;
+        $mess["total_bonuses"] = $user_balans;
+        $mess["bonus_level"] = $bonusLevel;
+        $mess["burn_balans"] = $user->burn_balans;
+        $mess["available_bonuses"] = $available_bonus;
+        $mess["message_cashier"] = $txt . " Спросите последние 4 цифры телефона который позвонит клиенту $user_balans";
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        file_put_contents(self::OUT_DIR . '/get_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK ', FILE_APPEND);
+        return $this->asJson($mess);
+    }
+
+//    public function actionSendMessage()
+//    {
+//        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+//        $data = file_get_contents('php://input');
+//        $result = json_decode($data, true);
+//
+//        $__API_PARAMS = ['store_id', 'seller_id', 'phone'];
+//
+//        foreach ($__API_PARAMS as $paramName) {
+//            if (empty($result[$paramName])) {
+//
+//                if ($paramName != 'phone') {
+//                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+//                }
+//
+//                return $this->asJson(["error" => "$paramName is required"]);
+//            }
+//        }
+//
+//        $phone = ClientHelper::phoneClear($result['phone']);
+//        if (!ClientHelper::phoneVerify($phone)) {
+//            return $this->asJson(["error_id" => 0.2, "error" => "phone is required"]);
+//        }
+//        $result['phone'] = $phone;
+//
+//        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->andWhere(['black_list' => '0'])->one();
+//        if (!$user) {
+//            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";
+//
+//            return $this->asJson($mess);
+//        }
+//        $keycode = $user->keycode;
+//
+//        $mess = [];
+//        $mess["result"] = true;
+//
+//        $mess['auth_code'] = $user->keycode;
+//        $mess['message_cashier'] = 'Отсканируйте QR код из телеграм бота или введите его руками';
+//
+//        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+//
+//        return $this->asJson($mess);
+//    }
+
+    public function actionSendMessage() {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $__API_PARAMS = ['store_id', 'seller_id', 'phone'];
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error" => "$paramName is required"]);
+            }
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 0.2, "error" => "phone is required"]);
+        }
+        $result['phone'] = $phone;
+
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->andWhere(['black_list' => '0'])->one();
+        if (!$user) {
+            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";
+
+            return $this->asJson($mess);
+        }
+
+        $mess["message_cashier"] = "Звонок-последние 4 цифры телефона";
+
+        $userAuthCallLog = UsersAuthCallLog::find()->select(['COUNT(*) as cnt'])->where(['phone' => $result['phone']])
+            ->andWhere(['store_id' => $result['store_id']])->andWhere(['>=', 'date', date('Y-m-d H:i:s', strtotime('-10 minutes'))])->one();
+
+        $cnt = $userAuthCallLog ? $userAuthCallLog->cnt : 1;
+
+        $keycode = '';
+
+        if ($cnt < 2) {
+            $body = @file_get_contents("https://sms.ru/code/call?phone=" . $result['phone'] . "&api_id=4DFE45F9-1897-79C0-6872-08F05D6B7FA4&ip=" . $_SERVER["REMOTE_ADDR"]);
+            $json_res = json_decode($body, true, 512, JSON_UNESCAPED_UNICODE);
+            if ($json_res["status"] == "OK") {
+                $keycode = $json_res["code"];
+                $user->keycode = '' . $keycode;
+                $user->password = ClientHelper::generatePassword(8);;
+                $user->save();
+                if ($user->getErrors()) {
+                    LogService::apiErrorLog(json_encode(["error_id" => 3.1415, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+                    return $this->asJson(["error_id" => 3.1415, "error" => $user->getErrors()]);
+                }
+                $mess["auth_code"] = $keycode;
+                $mess["message_cashier"] = "Попытка:$cnt Звонок клиенту! последние 4 цифры номера";
+            }
+        } elseif ($cnt > 2) {
+            $mess["message_cashier"] = "Попытка $cnt -извиняемся перед клиентом";
+        }
+        $name = "$keycode Попытка $cnt  " . $_SERVER["REMOTE_ADDR"];
+        $userAuthCallLog = new UsersAuthCallLog;
+        $userAuthCallLog->date = date('Y-m-d H:i:s');
+        $userAuthCallLog->store_id = $result['store_id'];
+        $userAuthCallLog->seller_id = $result['seller_id'];
+        $userAuthCallLog->phone = $result['phone'];
+        $userAuthCallLog->name = $name;
+        $userAuthCallLog->save();
+        if ($userAuthCallLog->getErrors()) {
+            LogService::apiErrorLog(json_encode(["error_id" => 4.15, "error" => $userAuthCallLog->getErrors()], JSON_UNESCAPED_UNICODE));
+            return $this->asJson(["error_id" => 4.15, "error" => $userAuthCallLog->getErrors()]);
+        }
+
+        Yii::info("keykod={$user->keycode} store_id={$result['store_id']} seller_id={$result['seller_id']} phone={$result['phone']} $name", self::LOG_CATEGORY_BONUS);
+
+        $mess["timeout"] = 15;
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson($mess);
+    }
+
+//        $mess["message_cashier"] = "Звонок-последние 4 цифры телефона";
+//
+//        $userAuthCallLog = UsersAuthCallLog::find()->select(['COUNT(*) as cnt'])->where(['phone' => $result['phone']])
+//            ->andWhere(['store_id' => $result['store_id']])->andWhere(['>=', 'date', date('Y-m-d H:i:s', strtotime('-10 minutes'))])->one();
+//
+//        $cnt = $userAuthCallLog ? $userAuthCallLog->cnt : 1;
+//
+//        if ($cnt < 2) {
+//            $body = @file_get_contents("https://sms.ru/code/call?phone=" . $result['phone'] . "&api_id=4DFE45F9-1897-79C0-6872-08F05D6B7FA4&ip=" . $_SERVER["REMOTE_ADDR"]);
+//            $json_res = json_decode($body, true, 512, JSON_UNESCAPED_UNICODE);
+//
+//            if ($json_res["status"] == "OK") {
+//                $keycode = $json_res["code"];
+//                $user->keycode = '' . $keycode;
+//                $user->password = ClientHelper::generatePassword(8);;
+//                $user->save();
+//
+//                if ($user->getErrors()) {
+//
+//                    LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+//
+//                    return $this->asJson(["error_id" => 3, "error" => $user->getErrors()]);
+//                }
+//
+//                $mess["auth_code"] = $keycode;
+//                $mess["message_cashier"] = "Попытка:$cnt Звонок клиенту! последние 4 цифры номера";
+//            }
+//        } else if ($cnt > 2) {
+//            $mess["message_cashier"] = "Попытка $cnt -извиняемся перед клиентом";
+//        }
+//
+//        $name = "$keycode Попытка $cnt  " . $_SERVER["REMOTE_ADDR"];
+//        $userAuthCallLog = new UsersAuthCallLog;
+//        $userAuthCallLog->date = date('Y-m-d H:i:s');
+//        $userAuthCallLog->store_id = $result['store_id'];
+//        $userAuthCallLog->seller_id = $result['seller_id'];
+//        $userAuthCallLog->phone = $result['phone'];
+//        $userAuthCallLog->name = $name;
+//        $userAuthCallLog->save();
+//        if ($userAuthCallLog->getErrors()) {
+//
+//            LogService::apiErrorLog(json_encode(["error_id" => 4, "error" => $userAuthCallLog->getErrors()], JSON_UNESCAPED_UNICODE));
+//
+//            return $this->asJson(["error_id" => 4, "error" => $userAuthCallLog->getErrors()]);
+//        }
+//
+
+    public function actionSaveClientInfo()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $fl = date('_Y_m_d__H_i_s_');
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . $result['phone']);
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+
+        $__API_PARAMS = ['store_id', 'seller_id', 'phone']; // first_name, second_name, sex, birth_day, referral_id, comment, events
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $phone = ClientHelper::phoneClear($result['phone']);
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!ClientHelper::phoneVerify($phone)) {
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+        $result['phone'] = $phone;
+
+        $source = $result["source"] ?? 0;
+        $store_id = $result["store_id"];
+        $store_id_guid = $store_id;
+        $seller_id = $result["seller_id"];
+        $phone = $result["phone"];
+        $first_name = $result["first_name"] ?? "";
+        $second_name = $result["second_name"] ?? "";
+        $sex2 = $result["sex"] ?? "";
+        $birth_day = $result["birth_day"] ?? "";
+        $referral_id = $result["referral_id"] ?? null;
+        $comment = $result["comment"] ?? "";
+        $events = $result["events"] ?? [];
+        $sex = "man";
+        if ($sex2 == "male") {
+            $sex = "man";
+        }
+        if ($sex2 == "female") {
+            $sex = "women";
+        }
+//        if ($referral_phone == $phone) {
+//            $referral_phone = "";
+//        }
+
+        $mess = [];
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        /* @var $user Users */
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->andWhere(['black_list' => '0'])->one();
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($user) {
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $user->referral_id = $referral_id != $user->id ? $referral_id : null;
+            $user->pol = $sex;
+            $user->bdate = $birth_day;
+            $user->name = "$first_name $second_name";
+            $user->comment = $comment;
+            $user->password = ClientHelper::generatePassword(8);
+            $user->keycode = '' . rand(1000, 9999);
+            $user->source = $source == 2 ? 1 : 0;
+            $user->save(); // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            if ($user->getErrors()) {
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                LogService::apiErrorLog(json_encode(["error_id" => 2, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                return $this->asJson(["error_id" => 2, "error" => $user->getErrors()]);
+            }
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $userEventOld = UsersEvents::find()->where(['phone' => $phone])->orderBy(['date_add' => SORT_ASC])->one();
+            if ($userEventOld && $userEventOld->date_add < date('Y-m-d H:i:s', time() - 2 * 86400)) { // Дата добавление последнего события не старше двух дней
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $mess["result"] = true;
+                $mess["message_cashier"] = "Возможность внесения памятных дат ограничена";
+
+                LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+                return $this->asJson($mess);
+            }
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+            $nomer_event = 1;
+            $dates = [];
+
+            foreach ($events as $k => $mass) {
+                $date = $mass["date"] ?? '';
+                $event_id = intval($mass['event_id'] ?? 0);
+
+                $datea = explode("-", $date);
+                $date_end = date("Y", time() + self::$YEAR_PERIOD * 86400) . "-" . $datea[1] . "-" . $datea[2];
+                $userEvent2 = UsersEvents::find()->where(['phone' => $phone])->andWhere(['date_day' => $datea[2]])->andWhere(['date_month' => $datea[1]])->one();
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($userEvent2) {
+                    $userEvent2->delete();
+                }
+                $userEvent3 = new UsersEvents;
+                $userEvent3->number = $nomer_event;
+                $userEvent3->date = $date;
+                $userEvent3->tip_id = $event_id;
+                $userEvent3->phone = $phone;
+                $userEvent3->date_day = $datea[2];
+                $userEvent3->date_month = $datea[1];
+                $userEvent3->date_add = date('Y-m-d H:i:s');
+                $userEvent3->tip = strval('???');
+                $userEvent3->name = 'М';
+                $userEvent3->sex = 'm';
+                $userEvent3->date_edit = date("Y-m-d H:i:s");
+                $userEvent3->date_edit_info = date("Y-m-d H:i:s");
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $userEvent3->save(); // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.
+                if ($userEvent3->getErrors()) {
+                    file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                    LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $userEvent3->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                    return $this->asJson(["error_id" => 3, "error" => $userEvent3->getErrors()]);
+                }
+                $dates [] = $date;
+                $nomer_event++;
+            }
+        } else {
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $created_name = $seller_id;
+            $rand = rand(1000, 9999);
+            $name = "$first_name $second_name";
+            $pass = ClientHelper::generatePassword(8);
+            $product1 = Products1c::find()->select(['name'])->where(['tip' => 'admin'])->andWhere(['id' => $seller_id])->one();
+            $product2 = Products1c::find()->select(['name'])->where(['tip' => 'city_store'])->andWhere(['id' => $store_id])->one();
+
+            $created_name = $product1 ? $product1->name : '';
+            $created_store = $product2 ? $product2->name : '';
+
+            $store_id_new = ExportImportTable::find()->select(['entity_id'])->where(['entity' => 'city_store'])->andWhere(['export_id' => '1'])
+                ->andWhere(['export_val' => $store_id])->one();
+            $seller_id_new = ExportImportTable::find()->select(['entity_id'])->where(['entity' => 'admin'])->andWhere(['export_id' => '1'])
+                ->andWhere(['export_val' => $seller_id])->one();
+            if ($store_id_new) {
+                $store_id_int = $store_id_new->entity_id;
+            }
+            if ($seller_id_new) {
+                $seller_id_int = $seller_id_new->entity_id;
+            }
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            Users::deleteAll(['phone' => $phone, 'phone_true' => '0']);
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $user2 = new Users;
+            $user2->source = $source;
+            $user2->pol = $sex;
+            $user2->keycode = strval($rand);
+            $user2->phone = $phone;
+            $user2->name = $name;
+            $user2->name_name = $first_name;
+            $user2->name_last = $second_name;
+            $user2->password = $pass;
+            $user2->phone_true = strval(1);
+            $user2->bdate = $birth_day;
+            $user2->referral_id = $referral_id;
+            $user2->comment = $comment;
+            $user2->created_id = $seller_id_int ?? 0;
+            $user2->created_name = $created_name;
+            $user2->seller_id = strval($seller_id);
+            $user2->store_id = $store_id_guid;
+            $user2->created_store_id = $store_id_int ?? 0;
+            $user2->created_store = $created_store;
+            $user2->date = date('Y-m-d H:i:s');
+            $user2->sale_store_id = $store_id_int ?? 0;
+            $user2->sale_store = '';
+            $user2->sms_info = 1;
+            $user2->reklama_info = 1;
+            $user2->info = '';
+            $setka_id = 1;
+            $user2->setka_id = $setka_id;
+            $user2->card = "" . ($phone * 2 + 1608 + $setka_id); // генерируем номер карты который зависит от номера сетки + ДР Тимура
+            $user2->save();  // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            if ($user2->getErrors()) {
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                LogService::apiErrorLog(json_encode(["error_id" => 4, "error" => $user2->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                return $this->asJson(["error_id" => 4, "error" => $user2->getErrors()]);
+            }
+
+            file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            if ($store_id == '56524cb1-4763-11ea-8cce-b42e991aff6c') {
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $admin_id = ClientHelper::getExportId($seller_id, "admin", 1);
+
+                $usersBonus = new UsersBonus;
+                $usersBonus->date = date('Y-m-d H:i:s');
+                $usersBonus->tip = 'plus';
+                $usersBonus->tip_sale = 'podarok';
+                $usersBonus->phone = $phone;
+                $usersBonus->name = "Приветственные бонусы посетителю сайта";
+                $usersBonus->store_id = $store_id_int ?? 0;
+                $usersBonus->site_id = 0;
+                $usersBonus->referal_id = 0;
+                $usersBonus->admin_id = $admin_id;
+                $usersBonus->price = 0;
+                $usersBonus->price_skidka = 0;
+                $usersBonus->bonus = 50;
+                $usersBonus->store_id_1c = $store_id;
+                $usersBonus->seller_id_1c = $seller_id;
+                $usersBonus->date_start = date('Y-m-d 08:00:00', strtotime('+1 day', strtotime($usersBonus->date)));
+                $usersBonus->date_end = date('Y-m-d H:i:s', strtotime('+1 week', strtotime($usersBonus->date_start)));
+                $usersBonus->save();
+                file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($usersBonus->getErrors()) {
+                    file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                    LogService::apiErrorLog(json_encode(["error_id" => 5, "error" => $usersBonus->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                    return $this->asJson(["error_id" => 5, "error" => $usersBonus->getErrors()]);
+                }
+            }
+        }
+
+        $mess["result"] = true;
+        $mess["message_cashier"] = "Данные клиента сохранены";
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        file_put_contents(self::OUT_DIR . '/save_client_info_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK ', FILE_APPEND);
+        return $this->asJson($mess);
+    }
+
+    public function actionSale()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+        $resultTest = $result;
+        $fl = date('_Y_m_d__H_i_s_');
+        $json=json_encode($resultTest,JSON_UNESCAPED_UNICODE);
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '_info.json', PHP_EOL . '--' . $result['phone']);
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '_info.json', ' '.date("d.m.Y H:i:s",time()).' JSON: '.$json.'  ', FILE_APPEND);
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . $result['phone']);
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        $__API_PARAMS = ['store_id', 'seller_id', 'phone', 'check_amount', 'check_id', 'check_name']; // items, auth_code, write_off_bonuses
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        foreach ($__API_PARAMS as $paramName) {
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-error-' . __LINE__, FILE_APPEND);
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $phone = ClientHelper::phoneClear($result['phone']);
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!ClientHelper::phoneVerify($phone)) {
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-error-' . __LINE__, FILE_APPEND);
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $result['phone'] = $phone;
+        $result['items'] = $result['items'] ?? [];
+
+        $store_id = $result["store_id"];
+        $seller_id = $result["seller_id"];
+        $check_amount = $result["check_amount"];
+        $check_id = $result["check_id"];
+        $check_name = $result["check_name"];
+        $lid_id = $result["lid_id"] ?? 0;
+        $auth_code = $result['auth_code'] ?? 0;
+        $write_off_bonuses = intval($result["write_off_bonuses"] ?? 0); // только при продаже
+
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();
+        $bonusLevels = BonusLevels::find()->where(['active' => 1])->indexBy('alias')->asArray()->all();
+        $bonusLevel = $user->bonus_level ?? "silver";
+        $cashback_rate = isset($bonusLevels[$bonusLevel]['cashback_rate'])
+            ? $bonusLevels[$bonusLevel]['cashback_rate'] / 100
+            : self::$FIRST_SALE_PROCENT;
+
+//        $referal_rate = isset($bonusLevels[$bonusLevel]['referal_rate'])
+//            ? $bonusLevels[$bonusLevel]['referal_rate'] / 100
+//            : self::$CREDIT_HIGH_PROCENT;
+
+        $bonus_rate = isset($bonusLevels[$bonusLevel]['bonus_rate'])
+            ? $bonusLevels[$bonusLevel]['bonus_rate'] / 100
+            : self::$FIRST_SALE_PROCENT;
+
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $amount_real = 0;
+        $items_arr_no = array_values(ArrayHelper::map(
+            UniversalCatalogItem::find()->where(['catalog_alias' => 'unused_nomenclature'])->all(), 'guid', 'guid'));
+        $items_arr_no_bonus_writeoffs = array_values(ArrayHelper::map(
+            UniversalCatalogItem::find()->where(['catalog_alias' => 'non_bonusable_goods'])->all(), 'guid', 'guid'));
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $summa_no = 0;
+        $summa_no_writeoffs = 0;
+        $amount_all = 0;
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        foreach ($result['items'] as $item) {
+            if (in_array($item["product_id"], $items_arr_no)) {
+                $summa_no = $summa_no + $item["price"] * $item["quantity"];
+            } else if (in_array($item["product_id"], $items_arr_no_bonus_writeoffs)) {
+                $summa_no_writeoffs = $summa_no_writeoffs + $item["price"] * $item["quantity"];
+            } else {
+                $amount_real = $amount_real + $item["price"] * $item["quantity"];
+            }
+            $amount_all = $amount_all + $item["price"] * $item["quantity"];
+        }
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        $cnt = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])->count());
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+       // $max_procent = $cnt == 0 ? self::$FIRST_SALE_PROCENT : ($cnt == 1 ? self::$SECOND_SALE_PROCENT : self::$MAX_PROCENT);
+        $max_procent = $bonus_rate;
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        // если списывается в попытке больше бонусов чем может списаться -
+        $percent = $phone == "79049031399" ? 0.9 : $max_procent;
+        $write_off_bonuses_theory = round($amount_real * $percent);
+        if ($write_off_bonuses > $write_off_bonuses_theory) {
+            $write_off_bonuses = $write_off_bonuses_theory;
+        }
+        $user_balans = ClientHelper::getBonusBalance($phone);
+        if ($user_balans < $write_off_bonuses) {
+            $write_off_bonuses = $user_balans;
+        }
+
+        // TO8-22: Промо-списание БЛАГО
+        // Проверяем: если у клиента есть промо-баланс >= 350, покупка >= 1700 и 350 > стандартного максимума — списываем промо
+        $usePromoWriteOff = false;
+        $promoWriteOffAmount = 350;
+        $promoMinCheckAmount = 1700;
+
+        $now = date('Y-m-d H:i:s');
+        $promoPlusSum = (float) UsersBonus::find()
+            ->where(['phone' => $phone, 'tip' => 'plus', 'tip_sale' => Promocode::TIP_SALE_PROMOBONUS])
+            ->andWhere(['<=', 'date_start', $now])
+            ->andWhere(['>=', 'date_end', $now])
+            ->sum('bonus');
+        $promoMinusSum = (float) UsersBonus::find()
+            ->where(['phone' => $phone, 'tip' => 'minus', 'tip_sale' => Promocode::TIP_SALE_PROMOBONUS])
+            ->sum('bonus');
+        $promoBalance = max(0, $promoPlusSum - $promoMinusSum);
+
+        if ($promoBalance >= $promoWriteOffAmount
+            && $amount_all >= $promoMinCheckAmount
+            && $promoWriteOffAmount > $write_off_bonuses_theory
+        ) {
+            $usePromoWriteOff = true;
+            $write_off_bonuses = $promoWriteOffAmount;
+        }
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        // сумма со скидкой
+        $summa_chek = $amount_all - $write_off_bonuses;
+        $baza_back = $amount_real + $summa_no_writeoffs - $write_off_bonuses;
+
+        $mess = [];
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        /** @var $user Users */
+        if (!$user) {
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $mess["error"] = "Покупателя " . $result['phone'] . " нет в бонусной программе!";
+
+            return $this->asJson($mess);
+        }
+        // TO8-22: При промо-списании burn_balans не трогаем — списываются промо-бонусы, а не обычные
+        if (!$usePromoWriteOff) {
+            $user->burn_balans = max(0, $user->burn_balans - $write_off_bonuses);
+        }
+        // [balans - burn_balance, burn_balans] - показать клиенту что мы сожгли сжигаемый баланс
+
+// старая точка проверки кода
+//        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+//        if ($user->keycode != strval($auth_code)) {
+//
+//            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-auth_code not valid-' . __LINE__ . ' keycode ' .$user->keycode . '|  auth_code ' . strval($auth_code), FILE_APPEND);
+//            return $this->asJson(['error' => 'auth_code not valid']);
+//        }
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $user_id = $user->id;
+//        $keycode = $user->keycode;
+//        $name = $user->name;
+//        $referral_id = $user->referral_id;
+//        $sale_avg_price = $user->sale_avg_price;
+        $sale_price = $user->sale_price;
+        $sale_cnt = $user->sale_cnt;
+//        if ($referral_id == $user_id) {
+//            $referral_id = 0;
+//        }
+        $ip = $_SERVER['REMOTE_ADDR'];
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+
+        $store_id_1c = $store_id;
+        $site_id = 0;
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        // получаем внутренний ID продаца - сотрудника из таблицы admin
+        $admin_id = ClientHelper::getExportId($seller_id, "admin", 1);
+        // получаем внутренний ID продаца - сотрудника из таблицы admin
+        $store_id = ClientHelper::getExportId($store_id_1c, "city_store", 1);
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $writeOffAlready = false;
+        if (!empty($lid_id)) {
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $tipSaleForCheck = $usePromoWriteOff ? Promocode::TIP_SALE_PROMOBONUS : 'sale';
+            $writeOffAlready = UsersBonus::find()->where(['lid_id' => $lid_id, 'phone' => $phone, 'tip_sale' => $tipSaleForCheck, 'tip' => 'minus'])->one() != null;
+        }
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+        $user_balans_new = $user_balans;
+        if ($write_off_bonuses && !$writeOffAlready) {
+            // TO8-22: При промо-списании auth_code не требуется (списание автоматическое)
+            if (!$usePromoWriteOff) {
+                // Проверка кода только при стандартном списании
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($user->keycode != strval($auth_code)) {
+
+                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '-auth_code not valid-' . __LINE__ . ' keycode ' .$user->keycode . '|  auth_code ' . strval($auth_code), FILE_APPEND);
+                    return $this->asJson(['error' => 'auth_code not valid']);
+                }
+            }
+
+            // TO8-22: При промо-списании обычный баланс не уменьшается — списываются промо-бонусы
+            $user_balans_new = $usePromoWriteOff ? $user_balans : ($user_balans - $write_off_bonuses);
+            $tipSaleForWriteOff = $usePromoWriteOff ? Promocode::TIP_SALE_PROMOBONUS : 'sale';
+            $name_b = $usePromoWriteOff
+                ? "Списание промо-бонусов БЛАГО по чеку $check_name"
+                : "Спиcание бонусов по чеку $check_name";
+            $usersBonus = new UsersBonus;
+            $usersBonus->date = date('Y-m-d H:i:s');
+            $usersBonus->tip = 'minus';
+            $usersBonus->tip_sale = $tipSaleForWriteOff;
+            $usersBonus->phone = $phone;
+            $usersBonus->name = $name_b;
+            $usersBonus->check_id = $check_id;
+            $usersBonus->store_id = $store_id;
+            $usersBonus->ip = $ip;
+            $usersBonus->site_id = $site_id; // ???
+            $usersBonus->referal_id = 0;// $referal_id;
+            $usersBonus->admin_id = $admin_id;
+            $usersBonus->price = $summa_chek;
+            $usersBonus->price_skidka = $write_off_bonuses;
+            $usersBonus->bonus = $write_off_bonuses;
+            $usersBonus->store_id_1c = $store_id_1c;
+            $usersBonus->seller_id_1c = $seller_id;
+            $usersBonus->user_id = $user_id;             // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+            $usersBonus->lid_id = $lid_id;               // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+            $usersBonus->date_start = $usersBonus->date; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+            $usersBonus->date_end = $usersBonus->date;   // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+            $usersBonus->date_dell = $usersBonus->date;  // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+            $usersBonus->save(); // иначе не пройдём валидацию, т.к. множество полей в бд не заполнены.
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+            if ($user->first_minus_balance === null) {
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $user->first_minus_balance = $usersBonus->date;
+                $user->save();
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            }
+
+            if ($usersBonus->getErrors()) {
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                LogService::apiErrorLog(json_encode(["error_id" => 4, "error" => $usersBonus->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                return $this->asJson(["error_id" => 4, "error" => $usersBonus->getErrors()]);
+            }
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            Yii::info("MINUS write_off_bonuses={$write_off_bonuses}", self::LOG_CATEGORY_BONUS);
+        }
+        // TO8-22: При промо-списании кэшбек НЕ начисляется
+        if ($usePromoWriteOff) {
+            Yii::info("PROMO write_off={$write_off_bonuses}, no cashback for {$phone}", self::LOG_CATEGORY_BONUS);
+
+            // Обновляем поля пользователя (аналогично стандартному пути)
+            $sale_price += $check_amount;
+            $sale_avg_price = round($sale_price / ($sale_cnt + 1));
+
+            $user->keycode = "" . rand(1000, 9999);
+            $user->password = ClientHelper::generatePassword(8);
+            $user->date_last_sale = date('Y-m-d H:i:s');
+            $user->sale_cnt = $sale_cnt + 1;
+            if ($user->sale_cnt == 1) {
+                $user->date_first_sale = $user->date_last_sale;
+            }
+            $user->sale_store_id = $store_id;
+            $user->sale_price = $sale_price;
+            $user->sale_avg_price = $sale_avg_price;
+            $user->check_id_last_sale = $check_id;
+            if (!$user->date) {
+                $user->date = (new \DateTime('now', new \DateTimeZone('Europe/Moscow')))->format('Y-m-d H:i:sP');
+            }
+            $user->balans = ClientHelper::getBonusBalance($phone);
+            $user->save();
+
+            if ($user->getErrors()) {
+                LogService::apiErrorLog(json_encode(["error_id" => 6, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+                return $this->asJson(["error_id" => 6, "error" => $user->getErrors()]);
+            }
+
+            $this->updateUserBonusLevel($user, $sale_price, $check_id, $check_name);
+
+            $mess["write_off_bonuses"] = $write_off_bonuses;
+            $mess["summa_chek"] = $summa_chek;
+            $mess["bonus_back"] = 0;
+            $mess["user_balans"] = $user_balans_new;
+            $mess["user_balans_actual"] = $user->balans;
+            $mess["promo_writeoff"] = true;
+
+            return $this->asJson($mess);
+        }
+
+        //начисляем кэшбек клиенту 10% от покупки - с базы за вычитом бонусов которые он списывает
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $userFound = Users::find()->where(['phone' => $result['phone']])->one();
+        /** @var $userFound Users */
+        $salesCount = -1; /* Из-за нулевого значения по умолчанию куча клиентов получило бонус 20% за покупку */
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($userFound && $userFound->telegram_created_at) {
+            $salesCount = intval(Sales::find()->where(['phone' => $result['phone'], 'operation' => Sales::OPERATION_SALE])
+                ->andWhere(['>=', 'date', $userFound->telegram_created_at])->count());
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        }
+        $credit_procent_index = $userFound && $userFound->source > 0 && $salesCount == 0 ? 1 : 0;
+
+
+        $back10 = $back20 = 0;
+        $back1 = $back = round($baza_back * $cashback_rate);
+        $nm = "Возврат с покупки " . (100 * $cashback_rate) . "% $check_name сумма чека $check_amount";
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $userBonus2 = UsersBonus::find()->where(['phone' => $phone])->andWhere(['check_id' => $check_id])->andWhere(['site_id' => $site_id])
+            ->andWhere(['store_id' => $store_id])->andWhere(['tip' => 'plus'])->andWhere(['bonus' => $back])->andWhere(['name' => $nm])->one();
+        if (!$userBonus2) {
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $user_balans_new += $back;
+
+            $userBonus2 = new UsersBonus;
+            $userBonus2->tip = 'plus';
+            $userBonus2->tip_sale = 'sale';
+            $userBonus2->date = date('Y-m-d H:i:s');
+            $userBonus2->date_start = date('Y-m-d H:i:s', strtotime('+1 day', time()));
+            $userBonus2->date_end = date('Y-m-d H:i:s', strtotime('+' . self::$YEAR_PERIOD . ' day', time()));
+            $userBonus2->phone = $phone;
+            $userBonus2->name = $nm;
+            $userBonus2->check_id = $check_id;
+            $userBonus2->store_id = $store_id;
+            $userBonus2->bonus = $back;
+            $userBonus2->ip = $ip;
+            $userBonus2->site_id = $site_id;
+            $userBonus2->referal_id = 0; // $referal_id;
+            $userBonus2->admin_id = $admin_id;
+            $userBonus2->price = $summa_chek;
+            $userBonus2->store_id_1c = $store_id_1c;
+            $userBonus2->seller_id_1c = $seller_id;
+            $userBonus2->user_id = $user_id;
+            $userBonus2->lid_id = $lid_id;
+            $userBonus2->price_skidka = 0;
+            $userBonus2->date_dell = $userBonus2->date_end;
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            $userBonus2->save();
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            if ($userBonus2->getErrors()) {
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                LogService::apiErrorLog(json_encode(["error_id" => 5, "error" => $userBonus2->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                return $this->asJson(["error_id" => 5, "error" => $userBonus2->getErrors()]);
+            } else {
+                $back10 = $back;
+            }
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            Yii::info("PLUS bonus={$back}", self::LOG_CATEGORY_BONUS);
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            if ($credit_procent_index) {
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $back = round($baza_back * self::$CREDIT_HIGH_PROCENT_PART20);
+                $nm = "Возврат с покупки " . (100 * self::$CREDIT_HIGH_PROCENT_PART20) . "% $check_name сумма чека $check_amount";
+
+                $user_balans_new += $back;
+
+                $userBonus2 = new UsersBonus();
+                $userBonus2->tip = 'plus';
+                $userBonus2->tip_sale = 'sale';
+                $userBonus2->date = date('Y-m-d H:i:s');
+                $userBonus2->date_start = date('Y-m-d H:i:s', strtotime('+1 day', time()));
+                $userBonus2->date_end = date('Y-m-d H:i:s', strtotime('+3 month', time()));
+                $userBonus2->phone = $phone;
+                $userBonus2->name = $nm;
+                $userBonus2->check_id = $check_id;
+                $userBonus2->store_id = $store_id;
+                $userBonus2->bonus = $back;
+                $userBonus2->ip = $ip;
+                $userBonus2->site_id = $site_id;
+                $userBonus2->referal_id = 0;
+                $userBonus2->admin_id = $admin_id;
+                $userBonus2->price = $summa_chek;
+                $userBonus2->store_id_1c = $store_id_1c;
+                $userBonus2->seller_id_1c = $seller_id;
+                $userBonus2->user_id = $user_id;
+                $userBonus2->lid_id = $lid_id;
+                $userBonus2->price_skidka = 0;
+                $userBonus2->date_dell = $userBonus2->date_end;
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $userBonus2->save();
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($userBonus2->getErrors()) {
+
+                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                    LogService::apiErrorLog(json_encode(["error_id" => 5.2, "error" => $userBonus2->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                    return $this->asJson(["error_id" => 5.2, "error" => $userBonus2->getErrors()]);
+                } else {
+                    $back20 = $back;
+                }
+                if ($userFound->telegram_created_at == null) {
+                    $userFound->telegram_created_at = date("Y-m-d H:i:s");
+
+                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                    $userFound->save();
+                    if ($userFound->getErrors()) {
+
+                        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+
+                        LogService::apiErrorLog(json_encode(["error_id" => 5.3, "error" => $userFound->getErrors()], JSON_UNESCAPED_UNICODE));
+
+                        return $this->asJson(["error_id" => 5.3, "error" => $userFound->getErrors()]);
+                    }
+                }
+
+                $notifiableUser = new NotifiableUser;
+                $notifiableUser->phone = $phone;
+                $notifiableUser->type = "first_given_bonus";
+                $notifiableUser->data = "" . ($back1 + $back);
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                $notifiableUser->save();
+
+                file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($notifiableUser->getErrors()) {
+
+                    file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                    return $this->asJson(["error_id" => 5.4, "error" => $notifiableUser->getErrors()]);
+                }
+            }
+        }
+
+//        /////// Добавляем бонусов рефералу
+//        if ($referal_id && $back) {
+//            $name = "Вознаграждение за приведенного друга";
+//            $referalBonus = UsersBonus::find()->where(['phone' => $phone])->andWhere(['referal_id' => $referal_id])->andWhere(['tip' => 'plus'])->one(); // phone = referal_id ???
+//            if (!$referalBonus) {
+//                $referalBonus = new UsersBonus;
+//                $referalBonus->tip = 'plus';
+//                $referalBonus->tip_sale = 'referal';
+//                $referalBonus->date = date('Y-m-d H:i:s');
+//                $referalBonus->date_start = date('Y-m-d H:i:s', strtotime('+1 day', time()));
+//                $referalBonus->date_end = date('Y-m-d H:i:s', strtotime('+' . self::$YEAR_PERIOD . ' day', time()));
+//                $referalBonus->phone = $phone;
+//                $referalBonus->name = $name;
+//                $referalBonus->check_id = $check_id;
+//                $referalBonus->store_id = $store_id;
+//                $referalBonus->bonus = $back;
+//                $referalBonus->ip = $ip;
+//                $referalBonus->site_id = $site_id; // ??? $user_id_referal
+//                $referalBonus->referal_id = $referal_id; // ???
+//                $referalBonus->admin_id = $admin_id;
+//                $referalBonus->price = $summa_chek;
+//                $referalBonus->store_id_1c = $store_id_1c;
+//                $referalBonus->seller_id_1c = $seller_id;
+//                $referalBonus->save();
+//                if ($referalBonus->getErrors()) {
+//                    return $this->asJson(["error_id" => 3, "error" => $referalBonus->getErrors()]);
+//                }
+//            }
+//        }
+
+        ///////
+//        $itogo = 0;
+//        foreach ($result["items"] as $k => $mass) {
+//            $seller_id_item = $mass["seller_id"];
+//            $product_id = $mass["product_id"];
+//            $price = $mass["price"];
+//            $quantity = $mass["quantity"];
+//            $sm = $price * $quantity;
+//            //$info .=" id=$product_id ($quantity шт. x $price руб.)  = $sm руб.,";
+//            $itogo += $sm;
+//
+//            //получаем внутренний ID товара
+//            $item_id = ClientHelper::get_export_id($product_id, "products",1);
+//
+//            //товары к продаже
+//            $salesItem = new SalesItems;
+//            $salesItem->date = date('Y-m-d H:i:s');
+//            $salesItem->phone = $phone;
+//            $salesItem->check_id = $check_id;
+//            $salesItem->store_id = $store_id;
+//            $salesItem->store_id_1c = $store_id_1c;
+//            $salesItem->seller_id = $seller_id_item;
+//            $salesItem->admin_id = $admin_id;
+//            $salesItem->id_1c = $product_id;
+//            $salesItem->item_id = $item_id;
+//            $salesItem->kol = $quantity;
+//            $salesItem->summa = $sm;
+//            $salesItem->referal_id = 0; // $referal_id;
+//            $salesItem->color_id = 0; // $color_id ???
+//            $salesItem->lid_id = $lid_id;
+//            $salesItem->complect_id = 0;                 // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//            $salesItem->name = '???';                    // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//            $salesItem->skidka = $mass['discount'] ?? 0; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//            $salesItem->vozvrat = 0;                     // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//            $salesItem->save();
+//            if ($salesItem->getErrors()) {
+//                return $this->asJson(["error_id" => 4, "error" => $salesItem->getErrors()]);
+//            }
+//        }
+        // sale_avg_price sale_price
+        $sale_price += $check_amount;
+        $sale_avg_price = round($sale_price / ($sale_cnt + 1));
+
+        $user->keycode = "" . rand(1000, 9999);
+        $user->password = ClientHelper::generatePassword(8);
+        $user->date_last_sale = date('Y-m-d H:i:s');
+        $user->sale_cnt = $sale_cnt + 1;
+        if ($user->sale_cnt == 1) {
+            $user->date_first_sale = $user->date_last_sale;
+        }
+        $user->sale_store_id = $store_id;
+        $user->sale_price = $sale_price;
+        $user->sale_avg_price = $sale_avg_price;
+        $user->check_id_last_sale = $check_id;
+        if (!$user->date) {
+            $user->date = (new DateTime('now', new DateTimeZone('Europe/Moscow')))->format('Y-m-d H:i:sP');
+        }
+        $user->balans = ClientHelper::getBonusBalance($phone);
+
+//        $user->email_old = "example@example.ru"; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->phone_old = "71111111111";        // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->check_id_forgot = "???";          // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->sid_forgot = "???";               // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->alerts_balans = "???";            // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->alerts_date = "???";              // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->alerts_reklama = "???";           // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $user->seller_id = "???";                // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $user->save();
+        if ($user->getErrors()) {
+
+
+            file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            LogService::apiErrorLog(json_encode(["error_id" => 6, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+
+            Yii::info("BEFORE END errors=" . json_encode($user->getErrors(), JSON_UNESCAPED_UNICODE), self::LOG_CATEGORY_BONUS);
+
+            return $this->asJson(["error_id" => 6, "error" => $user->getErrors()]);
+        } else {
+            $this->updateUserBonusLevel($user, $sale_price, $check_id, $check_name);
+        }
+        Yii::info("BEFORE END", self::LOG_CATEGORY_BONUS);
+//        $itogo -= $write_off_bonuses;
+
+//        // продажа заносим в таблицу
+//        $sale = new Sales;
+//        $sale->date = date("Y-m-d H:i:s");
+//        $sale->phone = $phone;
+//        $sale->operation = 'Продажа';
+//        $sale->store_id = $store_id;
+//        $sale->admin_id = $admin_id;
+//        $sale->seller_id = $seller_id;
+//        $sale->store_id_1c = $store_id_1c;
+//        $sale->id = $check_id;
+//        $sale->number = $check_name;
+//        $sale->summ = $amount_all;
+//        $sale->skidka = $write_off_bonuses;
+//        $sale->status = "???";        // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->payments = "???";      // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->pay_arr = "???";       // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->sales_check = "???";   // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->order_id = "";         // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->terminal_id = "???";   // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->terminal = "???";      // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->kkm_id = "???";        // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->held = 0;              // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->date_up = $sale->date; // Поле не заполнялось в старом апи, но без него бд выдаёт ошибку при сохранении
+//        $sale->save();
+//        if ($sale->getErrors()) {
+//            return $this->asJson(["error_id" => 6, "error" => $sale->getErrors()]);
+//        }
+
+        $mess["result"] = true;
+        $mess["message_cashier"] = "Бонусы списаны";
+        $mess["user_balans_old"] = $user_balans;
+        $mess["user_balans_new"] = $user_balans_new;
+        $mess["user_balans_actual"] = $user->balans;
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+        file_put_contents(self::OUT_DIR . '/sale_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK ', FILE_APPEND);
+
+        $totalBonus = $back10 + $back20;
+
+        $input = [
+            'phone' => $phone,
+            'bonusCount' => $totalBonus,
+            'purchaseDate' => date("Y-m-d H:i:s"),
+            'orderId' => $check_id,
+        ];
+
+        $userBonusSendToTgLogs = new UserBonusSendToTgLogs;
+        $userBonusSendToTgLogs->input_hash = md5(Json::encode($input));
+        $userBonusSendToTgLogs->input = Json::encode($input);
+        $userBonusSendToTgLogs->check_id = $check_id;
+        $userBonusSendToTgLogs->phone = $phone;
+        $userBonusSendToTgLogs->bonusCount = $totalBonus;
+        $userBonusSendToTgLogs->status = 1;
+        $userBonusSendToTgLogs->date = date('Y-m-d H:I:s');
+        $userBonusSendToTgLogs->save();
+        if ($userBonusSendToTgLogs->getErrors()) {
+            LogService::apiErrorLog(json_encode(["error_id" => 100.001, "error" => $userBonusSendToTgLogs->getErrors()], JSON_UNESCAPED_UNICODE));
+        }
+        Yii::$app->queue->push(new SendBonusInfoToSiteJob($input));
+
+//        SiteService::notifySiteAboutBonuses($phone, $totalBonus, date("Y-m-d H:i:s"), $check_id);
+
+        return $this->asJson($mess);
+    }
+
+    /**
+     * Создаёт новую запись в таблице UsersBonusLevels.
+     *
+     * @param Users $user
+     * @param string $bonusLevel
+     * @param string $check_id
+     * @param string $check_name
+     * @param string $createdAt
+     * @return bool
+     */
+    protected function createBonusHistoryRecord($user, $bonusLevel, $check_id, $check_name, $createdAt)
+    {
+        $bonusRecord = new UsersBonusLevels();
+        $bonusRecord->phone = $user->phone;
+        $bonusRecord->user_id = $user->id;
+        $bonusRecord->bonus_level = $bonusLevel;
+        $bonusRecord->date_from = $createdAt;
+        $bonusRecord->check_id = $check_id;
+        $bonusRecord->check_name = $check_name;
+        $bonusRecord->active = 1;
+
+        if (!$bonusRecord->save()) {
+            LogService::apiErrorLog(
+                json_encode(["error_id" => 100, "error" => $bonusRecord->getErrors()], JSON_UNESCAPED_UNICODE)
+            );
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Обновляет бонусный уровень пользователя.
+     *
+     * @param Users  $user       Модель пользователя.
+     * @param float  $sale_price Текущая сумма покупок.
+     * @param string $check_id   Идентификатор чека.
+     * @param string $check_name Имя (номер) чека.
+     */
+    protected function updateUserBonusLevel($user, $sale_price, $check_id, $check_name)
+    {
+        $bonusLevels = BonusLevels::find()
+            ->where(['active' => 1])
+            ->orderBy(['threshold' => SORT_ASC])
+            ->all();
+
+        $computedBonusLevel = null;
+        foreach ($bonusLevels as $level) {
+            if ($sale_price > $level->threshold) {
+                $computedBonusLevel = $level->alias;
+            }
+        }
+        $newBonusLevel = $computedBonusLevel ?? 'silver';
+
+        $existingHistoryLevel = UsersBonusLevels::find()
+            ->where(['or', ['phone' => $user->phone], ['user_id' => $user->id]])
+            ->andWhere(['active' => 1])
+            ->one();
+
+        $now = date('Y-m-d H:i:s');
+
+        if (empty($user->bonus_level) || $user->bonus_level !== $newBonusLevel) {
+            $user->bonus_level = $newBonusLevel;
+            if (!$user->save()) {
+                LogService::apiErrorLog(
+                    json_encode(["error_id" => 6.1, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE)
+                );
+            }
+
+            if ($existingHistoryLevel) {
+                $existingHistoryLevel->active = 0;
+                $existingHistoryLevel->date_to = $now;
+                if (!$existingHistoryLevel->save()) {
+                    LogService::apiErrorLog(
+                        json_encode(
+                            ["error_id" => 6.2, "error" => $existingHistoryLevel->getErrors()],
+                            JSON_UNESCAPED_UNICODE
+                        )
+                    );
+                }
+            }
+
+            $this->createBonusHistoryRecord($user, $newBonusLevel, $check_id, $check_name, $now);
+        }
+    }
+
+    public function actionGetClientInfo()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $__API_PARAMS = ['phone'];
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+
+        $mess = [];
+        $user = Users::find()->select(['id', 'keycode', 'bonus_level', 'burn_balans', 'name', 'referral_id', 'bdate', 'comment', 'pol', 'extract(epoch FROM  date) as date'])
+            ->where(['phone' => $phone])->one();
+        if (!$user) {
+            $mess["error"] = "Покупателя " . $phone . " нет в бонусной программе!";
+
+            return $this->asJson($mess);
+        }
+        $name = explode(" ", $user->name);
+        $birth_day = $user->bdate;
+        $first_name = $name[0] ?? '';
+        $second_name = $name[1] ?? '';
+        $comment = $user->comment;
+        $pol = "male";
+        if ($user->pol == "women") {
+            $pol = "female";
+        }
+        // если с момента добавления клиента прошло не более 5 часов позволяем редактировать даты иначе запрещаем редактирование
+        if ($user->date > time() - 3600 * 5) {
+            $mess["birth_day_readonly"] = true;
+            $mess["events_readonly"] = false;
+        }
+        if ($birth_day) {
+            $mess["birth_day_readonly"] = true;
+        }
+
+        $data = UsersEvents::find()->where(['phone' => $phone])->orderBy(['date' => SORT_DESC])->all();
+        foreach ($data as $row) {
+            if (strlen($row->date_day) == 1) {
+                $row->date_day = "0" . $row->date_day;
+            }
+            if (strlen($row->date_month) == 1) {
+                $row->date_month = "0" . $row->date_month;
+            }
+            if (!isset($mess["events"])) {
+                $mess["events"] = [];
+            }
+            $mess["events"][] = ["date" => $row->date, "event_id" => $row->tip_id];
+        }
+
+        $user_balance = ClientHelper::getBonusBalance($phone);
+
+        $mess["result"] = true;
+        $mess["sex"] = $pol;
+        $mess["first_name"] = $first_name;
+        $mess["second_name"] = $second_name;
+        $mess["birth_day"] = $birth_day;
+        $mess["comment"] = $comment;
+        $mess["balance"] = $user_balance;
+        $mess["bonus_level"] = $user->bonus_level;
+        $mess["burn_balans"] = $user->burn_balans;
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson($mess);
+    }
+
+    public function actionReturn()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $__API_PARAMS = ['store_id', 'check_id']; // check_name, seller_id
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (!isset($result[$paramName])) {
+
+                LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+
+        $store_id = $result["store_id"];
+//        $seller_id = $result["seller_id"] ?? '';
+        $check_id = $result["check_id"];
+//        $check_name = $result["check_name"] ?? '';
+
+        UsersBonus::deleteAll(['and', ['check_id' => $check_id],
+            ['>', 'date', date('Y-m-d H:i:s', strtotime('-3 day', time()))]]);
+
+        // api_logs... event: return, seller_id, when, check
+
+        LogService::apiLogs(1, json_encode(['Удачный возврат'], JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson(['ok']);
+    }
+
+    public function actionAuthCodeFail()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $__API_PARAMS = [/*'store_id', 'seller_id',*/
+            'phone'];
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+        $result['phone'] = $phone;
+
+//        $seller_id = $result['seller_id'];
+//        $store_id = $result['store_id'];
+
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();
+        if (!$user) {
+            $mess["error"] = "Покупателя $phone нет в бонусной программе!";
+
+            return $this->asJson($mess);
+        }
+
+        $user->keycode = "" . rand(1000, 9999);
+        $user->save();
+
+        if ($user->getErrors()) {
+
+            LogService::apiErrorLog(json_encode(["error_id" => 3, "error" => $user->getErrors()], JSON_UNESCAPED_UNICODE));
+
+            return $this->asJson(["error_id" => 3, "error" => $user->getErrors()]);
+        }
+
+        $mess = [];
+        $mess["result"] = true;
+        // api_logs seller, store
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson($mess);
+    }
+
+    public function actionCurrentItems()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $__API_PARAMS = ['store_id', 'seller_id', 'phone', 'amount_no_discount', 'amount_to_pay'];
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (empty($result[$paramName])) {
+
+                if ($paramName != 'phone') {
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+        $result['phone'] = $phone;
+
+//        $store_id = $result["store_id"];
+//        $seller_id = $result["seller_id"];
+        $phone = $result["phone"];
+//        $check_amount = intval($result["check_amount"] ?? 0);
+//        $check_id = $result["check_id"] ?? '';
+//        $check_name = $result["check_name"] ?? '';
+//        $items = $result['items'] ?? [];
+
+        $mess = [];
+
+        $user = Users::find()->where(['phone' => $result['phone']])->andWhere(['phone_true' => '1'])->one();
+        if (!$user) {
+            $mess["error"] = "Покупателя $phone нет в бонусной программе!";
+
+            return $this->asJson($mess);
+        }
+
+//        $user_balans = ClientHelper::getBonusBalance($phone);
+//        $max = $itogo * self::$MAX_PROCENT;  // максимально можем разрешить списывать до 30 процентов от суммы заказа
+//        $max = ceil($max);
+//        $available_bonus = $user_balans;
+//        if ($available_bonus > $max) { // если баллов бонусов больше чем 30 процентов списываем по максимуму 30
+//            $available_bonus = $max;
+//        }
+//        $baza = $check_amount - $available_bonus;
+//        $back = ceil(self::$CREDIT_PROCENT * $baza);
+
+
+//        foreach ($items as $k => $mass) {
+//            $seller_id_item = $mass["seller_id"];
+//            $product_id = $mass["product_id"];
+//            $price = $mass["price"];
+//            $quantity = $mass["quantity"];
+//            $sm = $price * $quantity;
+//            $info .=" id=$product_id ($quantity шт. x $price руб.)  = $sm руб.,";
+//            $itogo += $sm;
+//
+//            //получаем внутренний ID товара
+//            $item_id = ClientHelper::getExportId($product_id, "products",1);
+//        }
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson(["NOT IMPLEMENTED"]);
+    }
+
+    public function actionGetSettings()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        $__API_PARAMS = ['store_id', 'seller_id'];
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (!isset($result[$paramName])) {
+
+                LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+
+        $store_id = $result['store_id'];
+        $seller_id = $result['seller_id'];
+
+        $mess = [];
+        $mess["result"] = true;
+        $mess["attempts_auth_code"] = 5; // Количество возможных попыток ввода кода подтверждения
+        $mess["list_events"] = [
+            ["id" => 1, "name" => "День рождения"],
+            ["id" => 2, "name" => "8 марта"],
+            ["id" => 3, "name" => "День матери"],
+            ["id" => 4, "name" => "День влюбленных"],
+            ["id" => 5, "name" => "День свадьбы"],
+            ["id" => 6, "name" => "Другое"]
+        ];
+        $mess["send_current_items"] = false; // Отправлять или нет текущие позиции чека
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson($mess);
+    }
+
+    public function actionGetUnusedNumenclatur() {
+        $items_arr_no = array_values(ArrayHelper::map(
+            UniversalCatalogItem::find()->where(['catalog_alias' => 'unused_nomenclature'])->all(), 'guid', 'guid'));
+        return $this->asJson(['unused_nomenclature' => $items_arr_no]);
+    }
+
+    public function actionGetContest001Participant() {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+
+        $request = Yii::$app->request->getRawBody();
+
+        try {
+            $result = Json::decode($request);
+        } catch (\Exception $ex) {
+            return $this->asJson(['error' => ['code' => 400, 'message' => 'Json body invalid']]);
+        }
+
+        if (!isset($result['phone'])) {
+            return $this->asJson(["error_id" => 1, "error" => "phone is required"]);
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+        $result['phone'] = $phone;
+
+        $mess = [];
+
+        $contestants = Contest001::find()->where(['phone' => $phone])->all();
+        if (count($contestants) > 0) {
+            $mess['is_participant'] = true;
+            $raffle_numbers = [];
+            foreach ($contestants as $contestant) {
+                $raffle_numbers[] = $contestant->number;
+            }
+            $mess['raffle_numbers'] = implode(', ', $raffle_numbers);
+        } else {
+            $mess['is_participant'] = false;
+        }
+
+        LogService::apiLogs(1, json_encode($mess, JSON_UNESCAPED_UNICODE));
+
+        return $this->asJson(['response' => $mess]);
+    }
+
+    public function actionAdd() {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+
+        $request = Yii::$app->request->getRawBody();
+
+        $requestTest = json_decode(\Yii::$app->getRequest()->getRawBody(), true);
+
+        $fl = date('_Y_m_d__H_i_s_');
+
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', implode(',', $requestTest));
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        try {
+            $result = Json::decode($request);
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        } catch (\Exception $ex) {
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            return $this->asJson(['error' => ['code' => 400, 'message' => 'Json body invalid']]);
+        }
+
+        $__API_PARAMS = ['phone', 'description', 'tip_sale', 'bonus', 'date_end'];
+
+        foreach ($__API_PARAMS as $paramName) {
+            if (!isset($result[$paramName])) {
+                file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                if ($paramName != 'phone') {
+                    file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                    LogService::apiErrorLog(json_encode(["error_id" => 1, "error" => "$paramName is required"], JSON_UNESCAPED_UNICODE));
+                }
+                file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+                return $this->asJson(["error_id" => 1, "error" => "$paramName is required"]);
+            }
+        }
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        //
+        if (!in_array($result['tip_sale'], ['podarok', 'senat', 'nino802', 'sale', '14feb', '23feb', '8mar', 'quest001'])) {
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            return $this->asJson(["error_id" => 1.1, "error" => "tip_sale не разрешён (podarok, senat, nino802)"]);
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if (!ClientHelper::phoneVerify($phone)) {
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            return $this->asJson(["error_id" => 1.2, "error" => "phone is required"]);
+        }
+        $result['phone'] = $phone;
+
+        $stop = UsersStopList::find()->select(['phone'])->where(['phone' => $result['phone']])->one();
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($stop) {
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            return $this->asJson(["error_id" => 4, "error" => 'Номер телефона числится в стоп листе']);
+        }
+
+        $bonus = min((int)$result['bonus'], 1000);
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $found = UsersBonus::find()->where(['phone' => $phone])->andWhere(['>=', 'date_start', date('Y-m-d H:i:s', time() - self::$YEAR_PERIOD * 86400)])
+            ->andWhere(['tip_sale' => $result['tip_sale']])->andWhere(['tip' => 'plus'])->andWhere(['bonus' => $bonus])->one();
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($found) {
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            return $this->asJson(["error_id" => 3, "error" => 'Бонусы уже начисляли']);
+        }
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        $userBonus = new UsersBonus;
+        $userBonus->phone = $phone;
+        $userBonus->name = $result['description'];
+        $userBonus->date = date('Y-m-d H:i:s');
+        $userBonus->site_id = 1;
+        $userBonus->setka_id = 1;
+        $userBonus->tip = 'plus';
+        $userBonus->tip_sale = $result['tip_sale'];
+        $userBonus->bonus = $bonus;
+
+        $userBonusDateStart = $result['date_start'] ?? $userBonus->date;
+        $userBonus->date_start = date('Y-m-d H:i:s', strtotime($userBonusDateStart));
+
+        $userBonusDateEnd = $result['date_end'];
+        $userBonus->date_end = date('Y-m-d H:i:s', strtotime($userBonusDateEnd));
+
+        $userBonus->save();
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+        if ($userBonus->getErrors()) {
+            file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__, FILE_APPEND);
+            LogService::apiErrorLog(json_encode(["error_id" => 2, "error" => $userBonus->getErrors()], JSON_UNESCAPED_UNICODE));
+            return $this->asJson(["error_id" => 2, "error" => $userBonus->getErrors()]);
+        }
+        file_put_contents(self::OUT_DIR . '/add_bonuses_' . $fl . '.json', PHP_EOL . '--' . __LINE__ . ' OK', FILE_APPEND);
+        return $this->asJson(['response' => true]);
+    }
+
+    /**
+     * TO8-22: Активация промокода БЛАГО.
+     * POST /bonus/activate-promocode
+     * Параметры: phone, code
+     *
+     * Логика:
+     * 1. Найти промокод по коду
+     * 2. Проверить isActivatable()
+     * 3. Найти клиента по телефону
+     * 4. В транзакции: начислить 350 промо-бонусов (tip_sale='promobonus'), пометить промокод used=1
+     */
+    public function actionActivatePromocode()
+    {
+        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+        $data = file_get_contents('php://input');
+        $result = json_decode($data, true);
+
+        if (empty($result['code'])) {
+            return $this->asJson(["error_id" => 1, "error" => "code is required"]);
+        }
+        if (empty($result['phone'])) {
+            return $this->asJson(["error_id" => 4, "error" => "phone is required"]);
+        }
+
+        $phone = ClientHelper::phoneClear($result['phone']);
+        if (!ClientHelper::phoneVerify($phone)) {
+            return $this->asJson(["error_id" => 4, "error" => "phone is not valid"]);
+        }
+
+        $user = Users::find()
+            ->where(['phone' => $phone])
+            ->andWhere(['phone_true' => '1'])
+            ->one();
+
+        if (!$user) {
+            return $this->asJson(["error_id" => 4, "error" => "Пользователь с телефоном $phone не найден"]);
+        }
+
+        $transaction = Yii::$app->db->beginTransaction();
+        try {
+            // SELECT FOR UPDATE — блокируем строку промокода от параллельной активации
+            $promocode = Promocode::find()
+                ->where(['code' => $result['code']])
+                ->forUpdate()
+                ->one();
+
+            if (!$promocode) {
+                $transaction->rollBack();
+                return $this->asJson(["error_id" => 1, "error" => "Промокод не найден"]);
+            }
+
+            $activatable = $promocode->isActivatable();
+            if ($activatable !== true) {
+                $transaction->rollBack();
+                $errorMessages = [
+                    1 => "Промокод неактивен",
+                    2 => "Промокод уже использован",
+                    3 => "Срок действия промокода истёк",
+                ];
+                return $this->asJson([
+                    "error_id" => $activatable,
+                    "error" => $errorMessages[$activatable] ?? "Промокод недоступен",
+                ]);
+            }
+
+            $bonusAmount = $promocode->bonus ?: 350;
+            $duration = $promocode->duration ?: self::$YEAR_PERIOD;
+
+            $usersBonus = new UsersBonus();
+            $usersBonus->phone = $phone;
+            $usersBonus->name = "Активация промокода {$promocode->code}";
+            $usersBonus->date = date('Y-m-d H:i:s');
+            $usersBonus->tip = 'plus';
+            $usersBonus->tip_sale = Promocode::TIP_SALE_PROMOBONUS;
+            $usersBonus->bonus = $bonusAmount;
+            $usersBonus->price = 0;
+            $usersBonus->price_skidka = 0;
+            $usersBonus->user_id = $user->id;
+            $usersBonus->store_id = 0;
+            $usersBonus->site_id = 0;
+            $usersBonus->setka_id = 0;
+            $usersBonus->referal_id = 0;
+            $usersBonus->admin_id = 0;
+            $usersBonus->lid_id = 0;
+            $usersBonus->date_start = date('Y-m-d H:i:s');
+            $usersBonus->date_end = date('Y-m-d H:i:s', strtotime('+' . $duration . ' day'));
+            $usersBonus->date_dell = $usersBonus->date_end;
+            $usersBonus->ip = $_SERVER['REMOTE_ADDR'] ?? '';
+
+            if (!$usersBonus->save()) {
+                throw new \Exception('Ошибка сохранения бонуса: ' . json_encode($usersBonus->getErrors()));
+            }
+
+            $promocode->used = Promocode::USED_YES;
+            $promocode->activated_by = $user->id;
+            $promocode->activated_at = date('Y-m-d H:i:s');
+            if (!$promocode->save(false)) {
+                throw new \Exception('Ошибка обновления промокода: ' . json_encode($promocode->getErrors()));
+            }
+
+            $transaction->commit();
+
+            Yii::info("Промокод {$promocode->code} активирован для {$phone}, бонус={$bonusAmount}", self::LOG_CATEGORY_BONUS);
+
+            return $this->asJson([
+                "success" => true,
+                "bonus" => $bonusAmount,
+                "message" => "Промокод активирован, начислено {$bonusAmount} бонусов",
+            ]);
+        } catch (\Exception $e) {
+            $transaction->rollBack();
+            LogService::apiErrorLog(json_encode([
+                "error_id" => 10,
+                "error" => $e->getMessage(),
+                "phone" => $phone,
+                "code" => $result['code'],
+            ], JSON_UNESCAPED_UNICODE));
+            return $this->asJson(["error_id" => 10, "error" => "Ошибка активации промокода"]);
+        }
+    }
+}
diff --git a/plan_email.md b/plan_email.md
deleted file mode 100644 (file)
index fc1ac92..0000000
+++ /dev/null
@@ -1,293 +0,0 @@
-План: Улучшение системы регистрации и обработки писем Flowwow
-
-Контекст
-
-Крон-задача php yii marketplace/get-flowwow-orders читает письма от Flowwow через IMAP и создаёт/обновляет заказы в системе. Текущие проблемы:
-
-1. Нет разделения "регистрация" / "обработка" — email_status=1 ставится ДО processMessage() (строка 2203-2205), поэтому при сбое обработки письмо уже помечено как       
-   обработанное
-2. Повторная обработка невозможна — если saveEmailIfNotExists вернул null (письмо есть), processMessage() всё равно вызывается, но нет контроля завершённости обработки
-3. SEEN ставится только для новых заказов — processFlowwowOrders возвращает счётчик только для NEW (строка 2721), а SEEN зависит от $output > 0 (строка 2218). Для       
-   APPROVED/CHANGED/CANCELLED/DELIVERED SEEN не ставится
-4. Нет индексов на таблице marketplace_flowwow_emails для проверки дубликатов
-5. Нет отслеживания ошибок — если обработка упала, нет записи почему и сколько раз пытались
-6. Нет связки письмо↔заказ — невозможно из интерфейса писем найти, какие письма относятся к конкретному заказу
-
-Файлы для изменения
-┌──────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────┐
-│                           Файл                           │                                      Изменение                                       │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/migrations/m260218_*_improve_flowwow_emails.php    │ Новый — миграция: колонки + индексы                                                  │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/records/MarketplaceFlowwowEmails.php               │ Константы статусов, новые поля, relation, helper-методы                              │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/records/MarketplaceFlowwowEmailsSearch.php         │ Фильтрация по новым полям + поиск по заказу                                          │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/services/MarketplaceService.php                    │ Рефакторинг getFlowwowOrdersFromMail, saveEmailIfNotExists, fix processFlowwowOrders │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/commands/MarketplaceController.php                 │ Новый action actionRetryFlowwowEmails                                                │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/media/controllers/FlowwowController.php            │ Fix actionCheckMail (несовместимый вызов)                                            │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/views/marketplace-flowwow-emails/index.php         │ Обновление GridView: статусы, связка с заказом, поиск                                │
-├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤
-│ erp24/controllers/MarketplaceFlowwowEmailsController.php │ Мелкие правки (если нужны для view)                                                  │
-└──────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────┘
- ---
-Шаг 1. Миграция БД
-
-Файл: erp24/migrations/m260218_000001_improve_marketplace_flowwow_emails.php
-
-Добавить в таблицу marketplace_flowwow_emails:
-┌──────────────────────┬────────────────────┬─────────────────────────────────────────────────────────────────────┐
-│       Колонка        │        Тип         │                              Описание                               │
-├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤
-│ subject_type         │ smallint, NULL     │ Тип письма (1=NEW, 2=APPROVED, 3=CHANGED, 4=CANCELLED, 5=DELIVERED) │
-├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤
-│ processing_attempts  │ integer, default 0 │ Счётчик попыток обработки                                           │
-├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤
-│ processed_at         │ timestamp, NULL    │ Время успешной обработки                                            │
-├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤
-│ error_message        │ text, NULL         │ Текст последней ошибки                                              │
-├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤
-│ marketplace_order_id │ varchar(50), NULL  │ ID заказа (связка с marketplace_orders)                             │
-└──────────────────────┴────────────────────┴─────────────────────────────────────────────────────────────────────┘
-Индексы:
-- idx_flowwow_emails_dedup на (subject, "from", date) — ускорение проверки дубликатов
-- idx_flowwow_emails_status на (email_status) — выборка необработанных
-- idx_flowwow_emails_order на (marketplace_order_id) — связка с заказами
-
-Важно: from — зарезервированное слово PostgreSQL, использовать "from" в кавычках через raw SQL.
-
- ---
-Шаг 2. Модель MarketplaceFlowwowEmails
-
-Файл: erp24/records/MarketplaceFlowwowEmails.php
-
-2.1 Константы статусов
-public const STATUS_NEW = 0;        // Зарегистрировано, ожидает обработки
-public const STATUS_PROCESSED = 1;  // Успешно обработано
-public const STATUS_ERROR = 2;      // Ошибка (исчерпаны попытки)
-public const STATUS_RETRY = 3;      // Ожидает повторной обработки
-
-public const MAX_PROCESSING_ATTEMPTS = 5;
-
-2.2 Relation с MarketplaceOrders (связка письмо↔заказ)
-/**
-* Связь с заказом маркетплейса по marketplace_order_id.
-* Поле marketplace_order_id в emails хранит ID заказа из маркетплейса (например "123456"),
-* которому соответствует marketplace_orders.marketplace_order_id.
-  */
-  public function getOrder(): \yii\db\ActiveQuery
-  {
-  return $this->hasOne(MarketplaceOrders::class, ['marketplace_order_id' => 'marketplace_order_id'])
-  ->andWhere(['marketplace_id' => \yii_app\records\MarketplaceStore::FLOWWOW_WAREHOUSE_ID]);
-  }
-
-Это позволит:
-- Из письма перейти к заказу: $email->order
-- Из GridView показать ссылку на заказ
-
-2.3 Helper-методы
-
-- markAsProcessed(): bool — ставит STATUS_PROCESSED + processed_at
-- markAsError(string $errorMessage): bool — ставит STATUS_ERROR + error_message + инкремент attempts
-- markForRetry(string $reason): bool — ставит STATUS_RETRY + инкремент attempts
-- isRetryAllowed(): bool — processing_attempts < MAX_PROCESSING_ATTEMPTS
-- static findUnprocessed(): ActiveQuery — WHERE email_status IN (0, 3) AND processing_attempts < MAX
-- static statusLabels(): array — массив текстовых меток статусов
-- static subjectTypeLabels(): array — [1 => 'Новый заказ', 2 => 'Принят', 3 => 'Изменён', 4 => 'Отменён', 5 => 'Доставлен']
-
-2.4 Обновить rules() и attributeLabels()
-
-Добавить правила валидации и метки для новых полей.
-
- ---
-Шаг 3. Рефакторинг MarketplaceService
-
-3.1 Новый метод detectSubjectType
-private static function detectSubjectType(string $subject): ?int
-
-Определяет тип письма по теме через SUBJECT_INDEX regex patterns.
-
-3.2 Изменение saveEmailIfNotExists (строка 2260)
-
-При создании нового письма дополнительно заполнять subject_type через detectSubjectType(). Остальная логика без изменений — метод по-прежнему возвращает объект если     
-создано, null если уже существует.
-
-3.3 Рефакторинг getFlowwowOrdersFromMail (строки 2198-2242)
-
-Ключевое изменение — логика цикла обработки каждого письма:
-ДЛЯ КАЖДОГО ПИСЬМА ИЗ IMAP:
-├── saveEmailIfNotExists() → регистрация
-├── Если письмо новое (savedEmail != null):
-│   └── emailRecord = savedEmail
-├── Иначе: загружаем из БД:
-│   └── emailRecord = MarketplaceFlowwowEmails::find()->where(...)
-│
-├── Если emailRecord.email_status == STATUS_PROCESSED:
-│   ├── Только ставим SEEN на IMAP (чтобы не читать повторно)
-│   └── continue (пропускаем обработку)
-│
-├── Определяем паттерн темы → subject_index
-├── try:
-│   ├── processMessage($message) → обработка заказа
-│   ├── emailRecord->markAsProcessed() → статус ПОСЛЕ успешной обработки
-│   ├── emailRecord->marketplace_order_id = key($order)
-│   ├── imap_setflag_full(SEEN) → для ВСЕХ успешных, не только NEW
-│   └── countProcessedMessages++
-├── catch (Throwable):
-│   ├── Логирование ошибки
-│   ├── Если isRetryAllowed() → markForRetry()
-│   └── Иначе → markAsError()
-
-Что убираем:
-- Строки 2203-2205: преждевременная установка email_status = 1 ДО обработки
-- Строки 2218: условие if ($output > 0) для SEEN — теперь ставится безусловно
-
-3.4 Fix processFlowwowOrders (строка 2677)
-
-Добавить переменную $processingSuccess = false. Устанавливать в true при:
-- Создании нового заказа (строка 2719, $marketplaceOrder->save())
-- Успешном обновлении статуса (строки 2749, 2783, 2798)
-
-Изменить return (строка 2808):
-return $processingSuccess ? max($newOrdersCount, 1) : 0;
-
-Это обеспечит что $output > 0 для всех типов писем, а не только NEW.
-
-3.5 Новый метод processUnprocessedEmails
-public static function processUnprocessedEmails(?callable $progressCallback = null): array
-
-Выбирает из БД все письма со статусом STATUS_NEW или STATUS_RETRY (через findUnprocessed()), у которых заполнен subject_type, и обрабатывает каждое через
-processMessage().
-
-Возвращает: ['processed' => int, 'failed' => int, 'total' => int]
-
-Фильтр andWhere(['not', ['subject_type' => null]]) гарантирует, что старые записи (до миграции) не будут затронуты.
-
- ---
-Шаг 4. Консольная команда для retry
-
-Файл: erp24/commands/MarketplaceController.php
-
-Новый action:
-php yii marketplace/retry-flowwow-emails
-
-Вызывает MarketplaceService::processUnprocessedEmails() с progress callback в консоль. Выводит итоговую статистику.
-
- ---
-Шаг 5. Fix FlowwowController::actionCheckMail
-
-Файл: erp24/media/controllers/FlowwowController.php (строки 64-82)
-
-Текущий код несовместим: getFlowwowOrdersFromMail возвращает ['processed' => N, 'all' => M], а actionCheckMail вызывает count($messages) (вернёт 2) и
-processMessages($messages) (передаст массив с ключами 'processed'/'all' вместо писем).
-
-Исправить: убрать вызов processMessages, использовать возвращённый массив напрямую.
-
- ---
-Шаг 6. Обновить Search-модель (поиск писем по заказу)
-
-Файл: erp24/records/MarketplaceFlowwowEmailsSearch.php
-
-6.1 Новые правила фильтрации
-
-- В rules(): добавить subject_type, processing_attempts как integer; processed_at, error_message, marketplace_order_id как safe
-
-6.2 Поиск в search()
-// Фильтрация по новым полям
-$query->andFilterWhere([
-'subject_type' => $this->subject_type,
-'processing_attempts' => $this->processing_attempts,
-]);
-$query->andFilterWhere(['ilike', 'error_message', $this->error_message]);
-
-// Поиск писем по ID заказа маркетплейса
-$query->andFilterWhere(['ilike', 'marketplace_order_id', $this->marketplace_order_id]);
-
-Это позволит:
-- Ввести номер заказа Flowwow в фильтр → увидеть все письма, связанные с этим заказом
-- Фильтровать по типу письма (новый, принят, отменён и т.д.)
-- Фильтровать по статусу обработки
-
- ---
-Шаг 7. Обновить View (связка с заказами и поиск)
-
-Файл: erp24/views/marketplace-flowwow-emails/index.php
-
-7.1 Обновить статусы
-
-- Обновить отображение email_status — добавить статусы "Ошибка" (красный) и "Повтор" (синий)
-- Заменить текстовый фильтр email_status на dropdown:
-  'filter' => MarketplaceFlowwowEmails::statusLabels(),
-
-7.2 Добавить колонку "Тип письма"
-[
-'attribute' => 'subject_type',
-'value' => fn($model) => MarketplaceFlowwowEmails::subjectTypeLabels()[$model->subject_type] ?? '—',
-'filter' => MarketplaceFlowwowEmails::subjectTypeLabels(),
-],
-
-7.3 Добавить колонку "Заказ" со ссылкой
-[
-'attribute' => 'marketplace_order_id',
-'format' => 'raw',
-'value' => function ($model) {
-if (!$model->marketplace_order_id) {
-return '—';
-}
-$order = $model->order;
-if ($order) {
-return Html::a(
-'№' . $model->marketplace_order_id,
-['/marketplace-orders/view', 'id' => $order->id],
-['class' => 'btn btn-xs btn-outline-primary', 'target' => '_blank']
-);
-}
-return $model->marketplace_order_id . ' (не найден)';
-},
-'filter' => Html::input('text', 'MarketplaceFlowwowEmailsSearch[marketplace_order_id]',
-$searchModel->marketplace_order_id, ['class' => 'form-control', 'placeholder' => '№ заказа']),
-],
-
-Это даёт:
-- Поиск писем по заказу: ввести номер заказа в фильтр → увидеть ВСЕ связанные письма (создание, принятие, изменения, отмена, доставка)
-- Клик по номеру заказа → переход на страницу заказа в ERP
-- Фильтр по типу письма → например, увидеть все "отменённые" письма
-
-7.4 Добавить колонку "Попытки"
-'processing_attempts',
-
-7.5 Eager loading для оптимизации
-
-В контроллере MarketplaceFlowwowEmailsController::actionIndex добавить:
-$dataProvider->query->with(['order']);
-
- ---
-Порядок реализации
-1.  Миграция БД                              (независимый)
-2.  Модель MarketplaceFlowwowEmails           (зависит от 1)
-3.  MarketplaceFlowwowEmailsSearch            (зависит от 2)
-4.  MarketplaceService::detectSubjectType     (независимый)
-5.  MarketplaceService::saveEmailIfNotExists  (зависит от 2, 4)
-6.  MarketplaceService::processFlowwowOrders  (независимый fix)
-7.  MarketplaceService::getFlowwowOrdersFromMail (зависит от 5, 6)
-8.  MarketplaceService::processUnprocessedEmails (зависит от 2)
-9.  MarketplaceController::actionRetryFlowwowEmails (зависит от 8)
-10. FlowwowController::actionCheckMail fix   (зависит от 7)
-11. MarketplaceFlowwowEmailsController       (eager loading)
-12. View index.php                           (зависит от 2, 3, 11)
-
- ---
-Верификация
-
-1. Миграция: php yii migrate — проверить создание колонок и индексов
-2. Регистрация: запустить php yii marketplace/get-flowwow-orders, проверить что новые письма получили email_status=0, subject_type заполнен
-3. Обработка: проверить что после processMessage — email_status=1, processed_at заполнен, marketplace_order_id заполнен
-4. Повторный запуск: запустить команду снова — уже обработанные письма получают SEEN и пропускаются
-5. Retry: вручную UPDATE marketplace_flowwow_emails SET email_status=3 WHERE id=..., запустить php yii marketplace/retry-flowwow-emails — повторная обработка
-6. SEEN для всех типов: проверить что SEEN ставится для APPROVED, CANCELLED, DELIVERED (ранее не ставился)
-7. Поиск по заказу: в UI /marketplace-flowwow-emails/index ввести номер заказа Flowwow в фильтр → отображаются все связанные письма
-8. Ссылка на заказ: кликнуть по номеру заказа в колонке → переход на /marketplace-orders/view?id=...
-9. Фильтр по типу: выбрать "Отменён" в dropdown типа письма → только отменённые письма