productManagementSv = new ProductManagementService();
        $this->checkParamSv = new CheckParamService();
        $this->s3Client = new S3Client([
            'credentials' => [
                'key'    => env('AWS_S3_KEY'),
                'secret' => env('AWS_S3_SECRET'),
            ],
            'region'      => env('AWS_S3_REGION'),
            'version'     => 'latest',
        ]);
        // 時區調整
        date_default_timezone_set("Asia/Taipei");
    }
    
    /**
     * 列表頁
     *   本端點非 API,直接渲染到 view
     * @return array:          返回渲染頁
     */
    public function index()
    {
        // 渲染
        return view('admin.EcManagement.ProductManagement', [
            'can_step_0' => (Redis::get('TLW_0_MAN') || Redis::get('TLW_0_STOP')) ? false : true,
            'can_step_2' => (Redis::get('TLW_2_CSV')) ? false : true,
            'step_2_s3'  => env("AWS_S3_BUCKET"),
            'step_2_env' => env("APP_ENV"),
            'brands'     => $this->productManagementSv->getBrands(),
            'batchs'     => $this->productManagementSv->getBatchs(),
            'mstatuss'   => $this->productManagementSv->getMStatuss(),
        ]);
    }
    
    /**
     * 列表API
     *   接收搜尋參數與分頁等信息,返回JSON
     * @return string:         返回JSON
     */
    public function grid()
    {
        // 取得參數
        $param = $_GET;
        if ($param == null) exit();
        $draw = $param["draw"]; //客戶端傳來的查詢次數,無條件回傳用以核對
        $orderColumn = $param["order"][0]["column"] + 1; //前端從 0 開始送,但 mysql 從 1 開始算
        $orderDir = $param["order"][0]["dir"];
        $start = $param["start"];   // 頁碼
        $length = $param["length"]; // 一頁多大
        $searchValue = $param["search"]["value"];
        //客製化搜尋欄位
        $brand_no = $param["columns"][1]["search"]["value"];
        $batch_no = $param["columns"][2]["search"]["value"];
        $g_title = $param["columns"][3]["search"]["value"];
        $g_description = $param["columns"][4]["search"]["value"];
        $m_comment = $param["columns"][5]["search"]["value"];
        $m_status = $param["columns"][6]["search"]["value"];
        // 驗證
        if ($g_title != filter_var($g_title, FILTER_SANITIZE_SPECIAL_CHARS)) $g_title = "___CANNOT_FIND_STRING___";
        if (!$this->checkParamSv->LenMToN($g_title, 0, 50)) $g_title = "___CANNOT_FIND_STRING___";
        if ($g_description != filter_var($g_description, FILTER_SANITIZE_SPECIAL_CHARS)) $g_description = "___CANNOT_FIND_STRING___";
        if (!$this->checkParamSv->LenMToN($g_description, 0, 50)) $g_description = "___CANNOT_FIND_STRING___";
        if ($m_comment != filter_var($m_comment, FILTER_SANITIZE_SPECIAL_CHARS)) $m_comment = "___CANNOT_FIND_STRING___";
        if (!$this->checkParamSv->LenMToN($m_comment, 0, 50)) $m_comment = "___CANNOT_FIND_STRING___";
        //資料庫
        $recordsTotal = 0;
        $result = $this->productManagementSv->getProdocts(
            $recordsTotal,
            $orderColumn,
            $orderDir,
            $start,
            $length,
            $searchValue,
            $brand_no,
            $batch_no,
            $g_title,
            $g_description,
            $m_comment,
            $m_status
        );
        // 整理返回資料
        $data = array();
        for ($i = 0; $i < count($result); $i++) {
            $data[] = array(
                //一般資料
                $result[ $i ]["serno"],
                $result[ $i ]["g_id"],
                $result[ $i ]["g_title"],
                $result[ $i ]["g_description"],
                $result[ $i ]["g_image_link"],
                $result[ $i ]["g_price"],
                $result[ $i ]["g_sale_price"],
                $result[ $i ]["m_status"],
                $result[ $i ]["m_comment"],
            );
        }
        $json = array(
            "draw"            => $draw,
            "recordsTotal"    => $recordsTotal,
            "recordsFiltered" => $recordsTotal, //其實還是填入所有筆數,本次筆數可從陣列取得
            "data"            => $data,
        );
        
        // 返回
        return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true);
    }
    
    public function manualXml()
    {
        Redis::set('TLW_0_MAN', 'true');
        
        return $this->manualXmlStatus();
    }
    
    public function manualXmlStatus()
    {
        $json = [
            'can_step_0' => (Redis::get('TLW_0_MAN') || Redis::get('TLW_0_STOP')) ? false : true,
        ];
        
        return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true);
    }
    
    public function importCsv(Request $request)
    {
        // 上傳錯誤偵測(沒有包含檔案格式)
        switch ($_FILES['csv']['error']) {
            case UPLOAD_ERR_OK:
                break;
            case UPLOAD_ERR_NO_FILE:
                return json_decode(json_encode(['code' => 0, 'err' => '檔案大小錯誤'], JSON_NUMERIC_CHECK), true);
            case UPLOAD_ERR_INI_SIZE:
            case UPLOAD_ERR_FORM_SIZE:
                return json_decode(json_encode(['code' => 1, 'err' => '檔案太大'], JSON_NUMERIC_CHECK), true);
            default:
                return json_decode(json_encode(['code' => 1, 'err' => '系統錯誤'], JSON_NUMERIC_CHECK), true);
        }
        if ($_FILES['csv']['size'] > 100 * 1024 * 1024) {
            return json_decode(json_encode(['code' => 1, 'err' => '檔案最大100MB'], JSON_NUMERIC_CHECK), true);
        }
        // 路徑命名
        $path = '/tmp/tlwcsv/';
        if (!file_exists($path)) mkdir($path, 0777, true);
        chmod($path, 0777);
        $date = date('Y-m-d');
        if (!file_exists($path . 'tempImportCsv')) mkdir($path . 'tempImportCsv', 0777, true);
        chmod($path . 'tempImportCsv', 0777);
        if (!file_exists($path . 'tempImportCsv/' . $date)) mkdir($path . 'tempImportCsv/' . $date, 0777, true);
        chmod($path . 'tempImportCsv/' . $date, 0777);
        // 保存文件
        $time = time();
        move_uploaded_file($_FILES['csv']['tmp_name'], $path . 'tempImportCsv/' . $date . '/' . $time . '.csv');
        chmod($path . 'tempImportCsv/' . $date . '/' . $time . '.csv', 0777);
        // 異步處裡文件
        Redis::set('TLW_2_CSV', $path . 'tempImportCsv/' . $date . '/' . $time . '.csv' . '|' . $_POST['brand_no'] . '|' . $_POST['batch_no']);
        
        return json_decode(json_encode($_FILES, JSON_NUMERIC_CHECK), true);
    }
    
    public function importCsvStatus()
    {
        $json = [
            'can_step_2' => (Redis::get('TLW_2_CSV') || Redis::get('TLW_2_MAN')) ? false : true,
        ];
        
        return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true);
    }
    
    public function importCsvHandle($info)
    {
        // 上鎖
        Redis::set('TLW_2_MAN', 'true');
        // 剖析資訊
        $info = explode('|', $info);
        $csv = $info[0];
        $brand_no = $info[1];
        $batch_no = $info[2];
        // 更新產品訊息
        $fileR = fopen($csv, "r");
        while (!feof($fileR)) {
            $line = fgets($fileR);
            $cell = str_getcsv($line, ',', '"');
            if ($cell[0] != 'Item ID') {
                $g_id = $cell[0];
                $m_comment = $cell[4] ?? '';
                $m_status = $cell[5] ?? '';
                switch ($m_status) {
                    case GeneralConst::$mStatusMap[ GeneralConst::MSTATUS_WARNING ]['BNAME']:
                        $detail = $this->productManagementSv->getProductDetail($brand_no, $batch_no, $g_id);
                        $comment = (strpos($detail['m_comment'], $m_comment . ' / ') !== false) ? $detail['m_comment'] : $detail['m_comment'] . $m_comment . ' / ';
                        if ($detail['m_status'] == GeneralConst::MSTATUS_DISAPPROVED) {
                            // 原本是錯誤,但目前讀取到警告,就依然要保持錯誤狀態
                            $this->productManagementSv->updateProduct($brand_no, $batch_no, $g_id, GeneralConst::MSTATUS_DISAPPROVED, $comment);
                        } else {
                            // 原本是正常或警告,目前讀取到警告,就要將狀態設為警告
                            $this->productManagementSv->updateProduct($brand_no, $batch_no, $g_id, GeneralConst::MSTATUS_WARNING, $comment);
                        }
                        break;
                    case GeneralConst::$mStatusMap[ GeneralConst::MSTATUS_DISAPPROVED ]['BNAME']:
                        $detail = $this->productManagementSv->getProductDetail($brand_no, $batch_no, $g_id);
                        $comment = (strpos($detail['m_comment'], $m_comment . ' / ') !== false) ? $detail['m_comment'] : $detail['m_comment'] . $m_comment . ' / ';
                        // 原本不管是甚麼,目前讀取到錯誤,就要將狀態設為錯誤
                        $this->productManagementSv->updateProduct($brand_no, $batch_no, $g_id, GeneralConst::MSTATUS_DISAPPROVED, $comment);
                        break;
                }
            }
        }
        fclose($fileR);
        // 刪除檔案(權限問題無法刪除)
        unlink($csv);
    }
    
    public function manualEndpoint()
    {
        $param = $_GET;
        Redis::set('TLW_4_MAN', $param['brand_no'] . '|' . $param['batch_no']);
        
        return $this->manualEndpointStatus();
    }
    
    public function manualEndpointStatus()
    {
        $json = [
            'can_step_4' => (Redis::get('TLW_4_MAN') || Redis::get('TLW_4_STOP')) ? false : true,
        ];
        
        return json_decode(json_encode($json, JSON_NUMERIC_CHECK), true);
    }
    
    public function exportCsv(Request $request)
    {
        $title = [
            '系統號',
            '商品號',
            '品名',
            '描述',
            '網頁',
            '圖片路徑',
            '定價',
            '售價',
            '掃描結果',
            '掃描問題',
        ];
        $products = $this->productManagementSv->getExportProdocts($request);
        $this->productManagementSv->downloadExcel($title, $products, date("YmdHis") . '-REPORT-' . GeneralConst::$brandMap[ $request->brand_no ]['LB']);
    }
    
    // XML轉換腳本
    public function xml()
    {
        // 防重鎖
        if (!Redis::get('TLW_0_XML')) {
            Redis::set('TLW_0_XML', 'true');
            Redis::expire('TLW_0_XML', 1200); // 20 分鐘
            // 產生批次號
            $batch_no = date("Y-m-d H:i:s");
            $batch_no_digit = date("YmdHis");
            // 產生刪除批次號
            $del_batch_no = date('Y-m-d', strtotime(date("Y-m-d H:i:s") . "-14 days"));
            $del_batch_no_digit = date('Ymd', strtotime(date("Y-m-d") . "-14 days"));
            // 逐一品牌處理
            foreach (GeneralConst::$brandMap as $k => $v) {
                for ($i = 1; $i <= 14; $i++) {
                }
                // 載入品牌原始 XML
                $original = simplexml_load_file($v['URL']);
                // 開始寫入檔案
                $path = '/tmp/tlw/' . env('APP_ENV') . '/' . $v['LB'] . '/';
                if (!file_exists($path)) mkdir($path, 0777, true);
                $fileW = fopen($path . $batch_no_digit . '.xml', "w");
                fwrite($fileW, '特力屋' . $v['URL'] . '' . "\n");
                // 原始商品逐筆處裡(保留符號已被CDATA處理)
                // 注意可能出現相同品牌之中有相同 g_id 的現象,但由於不影響資料流程所以不處理
                $data = [];
                foreach ($original->Product as $p) {
                    // custom_label_1 為客戶系統自定義欄位,Y代表可讓GMC上架,N代表不可讓GM上架
                    if ((string)$p->custom_label_1 == GeneralConst::PUBLISH_YES) {
                        // XML
                        fwrite($fileW, '- ' . "\n");
                        fwrite($fileW, '' . (string)$p->SKU . '' . "\n");
                        fwrite($fileW, 'Name . ']]>' . "\n");
                        fwrite($fileW, 'Description . ']]>' . "\n");
                        fwrite($fileW, 'URL . ']]>' . "\n");
                        fwrite($fileW, 'LargeImage . ']]>' . "\n");
                        fwrite($fileW, '' . (string)$p->Condition . '' . "\n");
                        fwrite($fileW, '' . (string)'in stock' . '' . "\n");
                        fwrite($fileW, '' . (string)$p->Price . ' TWD' . '' . "\n");
                        fwrite($fileW, '' . (string)$p->SalePrice . ' TWD' . '' . "\n");
                        fwrite($fileW, '' . "\n");
                        fwrite($fileW, '' . (string)'' . '' . "\n");
                        fwrite($fileW, 'Category . ']]>' . "\n");
                        fwrite($fileW, 'custom_label_0 . ']]>' . "\n");
                        fwrite($fileW, '' . "\n");
                        // 資料庫(保留符號已被框架處理)
                        $data[] = [
                            'brand_no'         => $k,
                            'batch_no'         => $batch_no,
                            'g_id'             => (string)$p->SKU,
                            'g_title'          => (string)$p->Name,
                            'g_description'    => (string)$p->Description,
                            'g_link'           => (string)$p->URL,
                            'g_image_link'     => (string)$p->LargeImage,
                            'g_price'          => (string)$p->Price . ' TWD',
                            'g_sale_price'     => (string)$p->SalePrice . ' TWD',
                            'g_product_type'   => (string)$p->Category,
                            'g_custom_label_0' => (string)$p->custom_label_0,
                        ];
                        if (count($data) >= 100) {
                            $this->productManagementSv->insertProduct($data);
                            $data = [];
                        }
                    }
                }
                fwrite($fileW, '' . $batch_no . '' . "\n");
                $this->productManagementSv->insertProduct($data);
                // 結束寫入檔案
                fclose($fileW);
                // 檔案上傳至 S3
                $fileR = fopen($path . $batch_no_digit . '.xml', "r");
                $file_name = env('APP_ENV') . '/' . $v['LB'] . '/' . $batch_no_digit . '.xml';
                $this->s3Client->putObject([
                    'ACL'         => 'public-read',
                    'Body'        => $fileR,
                    'Bucket'      => env('AWS_S3_BUCKET'),
                    'ContentType' => 'application/xml',
                    'Key'         => $file_name,
                ]);
                fclose($fileR);
                // 刪除寫入的檔案
                unlink($path . $batch_no_digit . '.xml');
                // 刪除舊資料(XML)
                $objects = $this->s3Client->listObjects([
                    'Bucket' => env('AWS_S3_BUCKET'),
                    'Prefix' => env('APP_ENV') . '/' . $v['LB'] . '/' . $del_batch_no_digit,
                ]);
                foreach ($objects['Contents'] ?? [] as $object) {
                    $tmpkey = $object['Key'] ?? null;
                    if ($tmpkey) {
                        $this->s3Client->deleteObjects([
                            'Bucket' => env('AWS_S3_BUCKET'),
                            'Delete' => ['Objects' => [['Key' => $tmpkey]]],
                        ]);
                    }
                }
            }
            // 刪除舊資料(SQL)
            $this->productManagementSv->deleteProductByBatchNo($del_batch_no);
            Redis::del('TLW_0_XML');
        }
    }
    
    // XML轉換腳本(特力屋新版)
    public function xmlG()
    {
        // 防重鎖
        if (!Redis::get('TLW_0_XML')) {
            Redis::set('TLW_0_XML', 'true');
            Redis::expire('TLW_0_XML', 1200); // 20 分鐘
            // 產生批次號
            $batch_no = date("Y-m-d H:i:s");
            $batch_no_digit = date("YmdHis");
            // 產生刪除批次號
            $del_batch_no = date('Y-m-d', strtotime(date("Y-m-d H:i:s") . "-14 days"));
            $del_batch_no_digit = date('Ymd', strtotime(date("Y-m-d") . "-14 days"));
            // 逐一品牌處理
            foreach (GeneralConst::$brandMap as $k => $v) {
                for ($i = 1; $i <= 14; $i++) {
                }
                // 載入品牌原始 XML
                $original = simplexml_load_file($v['URLG']); // [新舊版本差異]來源修改
                // 開始寫入檔案
                $path = '/tmp/tlw/' . env('APP_ENV') . '/' . $v['LB'] . '/';
                if (!file_exists($path)) mkdir($path, 0777, true);
                $fileW = fopen($path . $batch_no_digit . '.xml', "w");
                fwrite($fileW, '特力屋' . $v['URL'] . '' . "\n");
                // 原始商品逐筆處裡(保留符號已被CDATA處理)
                // 注意可能出現相同品牌之中有相同 g_id 的現象,但由於不影響資料流程所以不處理
                $data = [];
                foreach ($original->channel->item as $p) { // [新舊版本差異]解析層級修改
                    // custom_label_1 為客戶系統自定義欄位,Y代表可讓GMC上架,N代表不可讓GM上架
                    if ((string)$p->custom_label_1 == GeneralConst::PUBLISH_YES) {
                        // XML
                        fwrite($fileW, '
- ' . "\n");
                        fwrite($fileW, '' . (string)$p->children('g', true)->id . '' . "\n");
                        fwrite($fileW, 'children('g', true)->title . ']]>' . "\n");
                        fwrite($fileW, 'children('g', true)->description . ']]>' . "\n");
                        fwrite($fileW, 'children('g', true)->link . ']]>' . "\n");
                        fwrite($fileW, 'children('g', true)->image_link . ']]>' . "\n");
                        fwrite($fileW, '' . (string)$p->children('g', true)->condition . '' . "\n");
                        fwrite($fileW, '' . (string)$p->children('g', true)->availability . '' . "\n");
                        fwrite($fileW, '' . (string)$p->children('g', true)->price . '' . "\n");
                        fwrite($fileW, '' . (string)$p->children('g', true)->sale_price . '' . "\n");
                        fwrite($fileW, 'children('g', true)->brand . ']]>' . "\n");
                        fwrite($fileW, '' . (string)'' . '' . "\n");
                        fwrite($fileW, 'children('g', true)->product_type . ']]>' . "\n");
                        fwrite($fileW, 'custom_label_0 . ']]>' . "\n");
                        fwrite($fileW, '' . "\n");
                        // 資料庫(保留符號已被框架處理)
                        $data[] = [
                            'brand_no'         => $k,
                            'batch_no'         => $batch_no,
                            'g_id'             => (string)$p->children('g', true)->id,
                            'g_title'          => (string)$p->children('g', true)->title,
                            'g_description'    => (string)$p->children('g', true)->description,
                            'g_link'           => (string)$p->children('g', true)->link,
                            'g_image_link'     => (string)$p->children('g', true)->image_link,
                            'g_price'          => (string)$p->children('g', true)->price,
                            'g_sale_price'     => (string)$p->children('g', true)->sale_price,
                            'g_product_type'   => (string)$p->children('g', true)->product_type,
                            'g_custom_label_0' => (string)$p->custom_label_0,
                        ];
                        if (count($data) >= 100) {
                            $this->productManagementSv->insertProduct($data);
                            $data = [];
                        }
                    }
                }
                fwrite($fileW, '' . $batch_no . '' . "\n");
                $this->productManagementSv->insertProduct($data);
                // 結束寫入檔案
                fclose($fileW);
                // 檔案上傳至 S3
                $fileR = fopen($path . $batch_no_digit . '.xml', "r");
                $file_name = env('APP_ENV') . '/' . $v['LB'] . '/' . $batch_no_digit . '.xml';
                $this->s3Client->putObject([
                    'ACL'         => 'public-read',
                    'Body'        => $fileR,
                    'Bucket'      => env('AWS_S3_BUCKET'),
                    'ContentType' => 'application/xml',
                    'Key'         => $file_name,
                ]);
                fclose($fileR);
                // 刪除寫入的檔案
                unlink($path . $batch_no_digit . '.xml');
                // 刪除舊資料(XML)
                $objects = $this->s3Client->listObjects([
                    'Bucket' => env('AWS_S3_BUCKET'),
                    'Prefix' => env('APP_ENV') . '/' . $v['LB'] . '/' . $del_batch_no_digit,
                ]);
                foreach ($objects['Contents'] ?? [] as $object) {
                    $tmpkey = $object['Key'] ?? null;
                    if ($tmpkey) {
                        $this->s3Client->deleteObjects([
                            'Bucket' => env('AWS_S3_BUCKET'),
                            'Delete' => ['Objects' => [['Key' => $tmpkey]]],
                        ]);
                    }
                }
            }
            // 刪除舊資料(SQL)
            $this->productManagementSv->deleteProductByBatchNo($del_batch_no);
            Redis::del('TLW_0_XML');
        }
    }
    
    // 更新動態饋給
    public function endpoint()
    {
        // 防重鎖
        if (!Redis::get('TLW_4_XML')) {
            Redis::set('TLW_4_XML', 'true');
            Redis::expire('TLW_4_XML', 1200); // 20 分鐘
            // 取出資料
            $info = explode('|', Redis::get('TLW_4_MAN'));
            $brand_no = $info[0];
            $batch_no = $info[1];
            $products = $this->productManagementSv->getEndpointProdocts($brand_no, $batch_no);
            // 拼裝XML
            // 開始寫入檔案
            $path = '/tmp/tlwendpoint/' . env('APP_ENV') . '/' . GeneralConst::$brandMap[ $brand_no ]['LB'] . '/';
            if (!file_exists($path)) mkdir($path, 0777, true);
            $fileW = fopen($path . 'FINAL.xml', "w");
            fwrite($fileW, '特力屋' . GeneralConst::$brandMap[ $brand_no ]['URL'] . '' . "\n");
            foreach ($products as $p) {
                fwrite($fileW, '
- ' . "\n");
                fwrite($fileW, '' . (string)$p['g_id'] . '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . (string)$p['g_condition'] . '' . "\n");
                fwrite($fileW, '' . (string)$p['g_availability'] . '' . "\n");
                fwrite($fileW, '' . (string)$p['g_price'] . '' . "\n");
                fwrite($fileW, '' . (string)$p['g_sale_price'] . '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . (string)$p['g_google_product_category'] . '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . "\n");
                fwrite($fileW, '' . "\n");
            }
            fwrite($fileW, '' . $batch_no . '' . "\n");
            // 結束寫入檔案
            fclose($fileW);
            // 上傳至S3取代
            $fileR = fopen($path . 'FINAL.xml', "r");
            $file_name = env('APP_ENV') . '/' . GeneralConst::$brandMap[ $brand_no ]['LB'] . '/FINAL.xml';
            $this->s3Client->putObject([
                'ACL'         => 'public-read',
                'Body'        => $fileR,
                'Bucket'      => env('AWS_S3_BUCKET'),
                'ContentType' => 'application/xml',
                'Key'         => $file_name,
            ]);
            fclose($fileR);
            // 刪除檔案
            unlink($path . 'FINAL.xml');
            Redis::del('TLW_4_XML');
        }
    }
    
}