Ozon Массовое создание заданий

Автоматическое создание заданий для внешнего продвижения с выбором товаров и настройками

Size

46.9 KB

Version

1.1.139

Created

Mar 13, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		Ozon Массовое создание заданий
3// @description		Автоматическое создание заданий для внешнего продвижения с выбором товаров и настройками
4// @version		1.1.139
5// @match		https://*.seller.ozon.ru/*
6// @icon		https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.xmlHttpRequest
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    console.log('🚀 Ozon Массовое создание заданий - запущен');
15
16    // ============================================
17    // АРХИТЕКТУРА РАСШИРЕНИЯ - ВАЖНО!
18    // ============================================
19    /*
20     * ОСНОВНЫЕ ПРИНЦИПЫ РАБОТЫ:
21     * 
22     * 1. ОДНА РАБОЧАЯ ВКЛАДКА:
23     *    - ГЛАВНАЯ вкладка - страница списка заданий
24     *      * Показывает модальное окно с настройками
25     *      * При запуске переходит на страницу создания задания
26     *      * После создания задания возвращается на страницу списка
27     *      * Цикл повторяется до тех пор, пока не закончатся товары
28     * 
29     * 2. ОБМЕН ДАННЫМИ через GM.getValue/GM.setValue:
30     *    - ozon_current_task: настройки и счётчик текущего процесса
31     *    - ozon_task_result: результат создания задания (success/error)
32     *    - ozon_processed_skus: список обработанных SKU (не создавать дубли)
33     * 
34     * 3. ПАГИНАЦИЯ товаров:
35     *    - Последняя кнопка с классом t0c110-a1.table-500 - это стрелка "вперёд"
36     *    - Если все товары на странице обработаны - переходим на следующую
37     *    - Рекурсивно ищем необработанный товар на всех страницах
38     * 
39     * 4. ЛОГИКА РАБОТЫ:
40     *    - Пользователь нажимает "Запустить" → переход на страницу создания
41     *    - Создаётся задание → переход на страницу списка
42     *    - Проверяется результат → переход на страницу создания для следующего задания
43     *    - Цикл продолжается в одной вкладке
44     */
45
46    // ============================================
47    // КОНСТАНТЫ И КОНФИГУРАЦИЯ
48    // ============================================
49    
50    const CONFIG = {
51        PAGES: {
52            LIST: 'https://seller.ozon.ru/app/advertisement/product/external-promo',
53            CREATE: 'https://seller.ozon.ru/app/advertisement/product/external-promo/create'
54        },
55        SELECTORS: {
56            // Селекторы для страницы списка заданий
57            LIST_PAGE: {
58                CREATE_BUTTON: 'button[data-context-help-target="external_promo_create_task"]',
59                HEADER: 'h1.c7r110-a'
60            },
61            // Селекторы для страницы создания задания
62            CREATE_PAGE: {
63                POST_TYPE_CLIPS: 'input[type="radio"][name="postType"][value="TASK_POST_TYPE_CLIP"]',
64                POST_TYPE_POSTS: 'input[type="radio"][name="postType"][value="TASK_POST_TYPE_POST"]',
65                BID_INPUT: 'div[data-context-help-target="external_promo_create_task_add_bid"] input',
66                SAMPLES_INPUT: 'div[data-context-help-target="external_promo_create_task_add_sample"] input',
67                ADD_PRODUCT_BUTTON: 'button.c9r110-a.c9r110-a4.body-500-true.c9r110-b4.c9r110-b9.c9r110-b0',
68                SAVE_BUTTON: 'button[type="submit"]'
69            },
70            // Селекторы для модального окна с товарами
71            MODAL: {
72                DIALOG: '.sc1110-a.sc1110-a0',
73                PRODUCT_ROWS: 'tbody tr.ct5110-c',
74                PRODUCT_ADD_BUTTON: 'td button[type="submit"].c9r110-a.c9r110-a4',
75                NEXT_PAGE_BUTTON: 'ul.t0c110-a button.t0c110-a1.table-500:last-child',
76                PAGINATION: 'ul.t0c110-a'
77            }
78        },
79        STORAGE_KEYS: {
80            PROCESSED_SKUS: 'ozon_processed_skus',
81            SETTINGS: 'ozon_bulk_settings',
82            IS_RUNNING: 'ozon_is_running'
83        },
84        DELAYS: {
85            AFTER_CLICK: 1000,
86            AFTER_PAGE_LOAD: 2000,
87            BETWEEN_ACTIONS: 500
88        }
89    };
90
91    // ============================================
92    // УТИЛИТЫ
93    // ============================================
94    
95    // Функция задержки
96    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
97
98    // Функция для безопасного получения элемента с ожиданием
99    async function waitForElement(selector, timeout = 10000) {
100        const startTime = Date.now();
101        while (Date.now() - startTime < timeout) {
102            const element = document.querySelector(selector);
103            if (element) {
104                console.log(`✅ Элемент найден: ${selector}`);
105                return element;
106            }
107            await delay(100);
108        }
109        console.error(`❌ Элемент не найден за ${timeout}мс: ${selector}`);
110        return null;
111    }
112
113    // Функция для клика с задержкой
114    async function clickElement(element, description = '') {
115        if (!element) {
116            console.error(`❌ Не могу кликнуть: элемент не найден ${description}`);
117            return false;
118        }
119        console.log(`🖱️ Клик: ${description}`);
120        element.click();
121        await delay(CONFIG.DELAYS.AFTER_CLICK);
122        return true;
123    }
124
125    // ============================================
126    // ХРАНИЛИЩЕ ДАННЫХ
127    // ============================================
128    
129    class Storage {
130        // Получить обработанные SKU
131        static async getProcessedSkus() {
132            const data = await GM.getValue(CONFIG.STORAGE_KEYS.PROCESSED_SKUS, '[]');
133            return JSON.parse(data);
134        }
135
136        // Добавить обработанный SKU
137        static async addProcessedSku(sku) {
138            const skus = await this.getProcessedSkus();
139            if (!skus.includes(sku)) {
140                skus.push(sku);
141                await GM.setValue(CONFIG.STORAGE_KEYS.PROCESSED_SKUS, JSON.stringify(skus));
142                console.log(`💾 SKU сохранён: ${sku}`);
143            }
144        }
145
146        // Проверить, обработан ли SKU
147        static async isSkuProcessed(sku) {
148            const skus = await this.getProcessedSkus();
149            return skus.includes(sku);
150        }
151
152        // Получить настройки
153        static async getSettings() {
154            const data = await GM.getValue(CONFIG.STORAGE_KEYS.SETTINGS, null);
155            return data ? JSON.parse(data) : null;
156        }
157
158        // Сохранить настройки
159        static async saveSettings(settings) {
160            await GM.setValue(CONFIG.STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
161            console.log('💾 Настройки сохранены:', settings);
162        }
163
164        // Проверить, запущен ли процесс
165        static async isRunning() {
166            return await GM.getValue(CONFIG.STORAGE_KEYS.IS_RUNNING, false);
167        }
168
169        // Установить статус запуска
170        static async setRunning(status) {
171            await GM.setValue(CONFIG.STORAGE_KEYS.IS_RUNNING, status);
172        }
173    }
174
175    // ============================================
176    // UI КОМПОНЕНТЫ
177    // ============================================
178    
179    class UI {
180        // Создать кнопку "Массовое создание заданий"
181        static createBulkButton() {
182            const button = document.createElement('button');
183            button.id = 'ozon-bulk-create-btn';
184            button.className = 'c9r110-a c9r110-a5 body-500-true c9r110-b2 c9r110-b9 c9r110-b0';
185            button.style.marginLeft = '10px';
186            button.innerHTML = `
187                <div class="c9r110-a0">
188                    <div class="c9r110-a1 c9r110-a2">
189                        <span class="c9r110-a2">⚡ Массовое создание заданий</span>
190                    </div>
191                </div>
192            `;
193            return button;
194        }
195
196        // Создать модальное окно настроек
197        static createSettingsModal() {
198            const modal = document.createElement('div');
199            modal.id = 'ozon-bulk-settings-modal';
200            modal.style.cssText = `
201                position: fixed;
202                top: 0;
203                left: 0;
204                width: 100%;
205                height: 100%;
206                background: rgba(0, 0, 0, 0.5);
207                display: flex;
208                align-items: center;
209                justify-content: center;
210                z-index: 100000;
211            `;
212
213            modal.innerHTML = `
214                <div style="
215                    background: white;
216                    border-radius: 12px;
217                    padding: 30px;
218                    width: 500px;
219                    max-width: 90%;
220                    box-shadow: 0 4px 20px rgba(0,0,0,0.3);
221                ">
222                    <h2 style="margin: 0 0 20px 0; font-size: 24px; color: #333;">
223                        ⚙️ Настройки массового создания
224                    </h2>
225                    
226                    <div style="margin-bottom: 20px;">
227                        <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #555;">
228                            Формат публикаций:
229                        </label>
230                        <select id="ozon-post-type" style="
231                            width: 100%;
232                            padding: 10px;
233                            border: 1px solid #ddd;
234                            border-radius: 6px;
235                            font-size: 14px;
236                        ">
237                            <option value="TASK_POST_TYPE_CLIP">Клипы</option>
238                            <option value="TASK_POST_TYPE_POST">Посты</option>
239                        </select>
240                    </div>
241
242                    <div style="margin-bottom: 20px;">
243                        <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #555;">
244                            Ставка за заказ (%):
245                        </label>
246                        <input type="number" id="ozon-bid" min="10" step="1" placeholder="Минимум 10%" style="
247                            width: 100%;
248                            padding: 10px;
249                            border: 1px solid #ddd;
250                            border-radius: 6px;
251                            font-size: 14px;
252                            box-sizing: border-box;
253                        ">
254                        <small style="color: #888; font-size: 12px;">Минимальная ставка — 10%</small>
255                    </div>
256
257                    <div style="margin-bottom: 25px;">
258                        <label style="display: block; margin-bottom: 8px; font-weight: 500; color: #555;">
259                            Количество образцов:
260                        </label>
261                        <input type="number" id="ozon-samples" min="0" step="1" placeholder="0" style="
262                            width: 100%;
263                            padding: 10px;
264                            border: 1px solid #ddd;
265                            border-radius: 6px;
266                            font-size: 14px;
267                            box-sizing: border-box;
268                        ">
269                        <small style="color: #888; font-size: 12px;">Можно использовать для товаров дешевле 5 000 ₽</small>
270                    </div>
271
272                    <div style="margin-bottom: 25px; padding: 15px; background: #fff3cd; border-radius: 6px; border: 1px solid #ffc107;">
273                        <div style="margin-bottom: 10px;">
274                            <strong style="color: #856404;">⚠️ Обработанные товары</strong>
275                        </div>
276                        <p style="margin: 0 0 10px 0; font-size: 13px; color: #856404;">
277                            Расширение запоминает товары, для которых уже создали задания, и пропускает их при повторном запуске.
278                        </p>
279                        <button id="ozon-reset-btn" style="
280                            padding: 8px 16px;
281                            border: 1px solid #ffc107;
282                            background: white;
283                            color: #856404;
284                            border-radius: 6px;
285                            cursor: pointer;
286                            font-size: 13px;
287                            font-weight: 500;
288                        ">
289                            🔄 Сбросить список обработанных товаров
290                        </button>
291                    </div>
292
293                    <div id="ozon-progress-container" style="display: none; margin-bottom: 20px;">
294                        <div style="background: #f0f0f0; border-radius: 6px; padding: 15px;">
295                            <div style="margin-bottom: 10px;">
296                                <strong>Прогресс:</strong> <span id="ozon-progress-text">0 / 0</span>
297                            </div>
298                            <div style="background: #ddd; height: 8px; border-radius: 4px; overflow: hidden;">
299                                <div id="ozon-progress-bar" style="
300                                    background: linear-gradient(90deg, #4CAF50, #45a049);
301                                    height: 100%;
302                                    width: 0%;
303                                    transition: width 0.3s ease;
304                                "></div>
305                            </div>
306                            <div style="margin-top: 10px; font-size: 12px; color: #666;">
307                                <span id="ozon-current-action">Ожидание...</span>
308                            </div>
309                        </div>
310                    </div>
311
312                    <div style="display: flex; gap: 10px; justify-content: flex-end;">
313                        <button id="ozon-cancel-btn" style="
314                            padding: 10px 20px;
315                            border: 1px solid #ddd;
316                            background: white;
317                            border-radius: 6px;
318                            cursor: pointer;
319                            font-size: 14px;
320                            font-weight: 500;
321                        ">
322                            Отмена
323                        </button>
324                        <button id="ozon-start-btn" style="
325                            padding: 10px 20px;
326                            border: none;
327                            background: #005bff;
328                            color: white;
329                            border-radius: 6px;
330                            cursor: pointer;
331                            font-size: 14px;
332                            font-weight: 500;
333                        ">
334                            🚀 Запустить
335                        </button>
336                        <button id="ozon-stop-btn" style="
337                            padding: 10px 20px;
338                            border: none;
339                            background: #ff4444;
340                            color: white;
341                            border-radius: 6px;
342                            cursor: pointer;
343                            font-size: 14px;
344                            font-weight: 500;
345                            display: none;
346                        ">
347                            ⏹️ Остановить
348                        </button>
349                    </div>
350                </div>
351            `;
352
353            return modal;
354        }
355
356        // Показать модальное окно
357        static showModal(modal) {
358            document.body.appendChild(modal);
359        }
360
361        // Скрыть модальное окно
362        static hideModal(modal) {
363            if (modal && modal.parentNode) {
364                modal.parentNode.removeChild(modal);
365            }
366        }
367
368        // Обновить прогресс
369        static updateProgress(current, total, action = '') {
370            const progressContainer = document.getElementById('ozon-progress-container');
371            const progressText = document.getElementById('ozon-progress-text');
372            const progressBar = document.getElementById('ozon-progress-bar');
373            const currentAction = document.getElementById('ozon-current-action');
374
375            if (progressContainer) {
376                progressContainer.style.display = 'block';
377                progressText.textContent = `${current} / ${total}`;
378                const percentage = total > 0 ? (current / total) * 100 : 0;
379                progressBar.style.width = `${percentage}%`;
380                if (currentAction && action) {
381                    currentAction.textContent = action;
382                }
383            }
384        }
385
386        // Показать кнопку остановки
387        static showStopButton() {
388            const startBtn = document.getElementById('ozon-start-btn');
389            const stopBtn = document.getElementById('ozon-stop-btn');
390            if (startBtn) startBtn.style.display = 'none';
391            if (stopBtn) stopBtn.style.display = 'block';
392        }
393
394        // Показать кнопку запуска
395        static showStartButton() {
396            const startBtn = document.getElementById('ozon-start-btn');
397            const stopBtn = document.getElementById('ozon-stop-btn');
398            if (startBtn) startBtn.style.display = 'block';
399            if (stopBtn) stopBtn.style.display = 'none';
400        }
401    }
402
403    // ============================================
404    // ОСНОВНАЯ ЛОГИКА АВТОМАТИЗАЦИИ
405    // ============================================
406    
407    class BulkTaskCreator {
408        constructor(settings) {
409            this.settings = settings;
410            this.currentPage = 1;
411            this.processedCount = 0;
412            this.totalProducts = 0;
413            this.shouldStop = false;
414        }
415
416        // Остановить процесс
417        stop() {
418            this.shouldStop = true;
419            console.log('⏹️ Остановка процесса...');
420        }
421
422        // Главная функция запуска
423        async start() {
424            console.log('🚀 Начало массового создания заданий');
425            console.log('⚙️ Настройки:', this.settings);
426
427            await Storage.setRunning(true);
428
429            try {
430                // Открываем страницу создания задания в новой вкладке
431                await this.createTasksLoop();
432            } catch (error) {
433                console.error('❌ Ошибка при создании заданий:', error);
434                alert('Произошла ошибка: ' + error.message);
435            } finally {
436                await Storage.setRunning(false);
437                UI.showStartButton();
438            }
439        }
440
441        // Цикл создания заданий
442        async createTasksLoop() {
443            console.log(`\n📄 Создание задания ${this.processedCount + 1}`);
444            
445            // Сохраняем текущее состояние
446            await GM.setValue('ozon_current_task', JSON.stringify({
447                settings: this.settings,
448                processedCount: this.processedCount,
449                shouldContinue: true
450            }));
451
452            // Очищаем предыдущий результат
453            await GM.setValue('ozon_task_result', null);
454
455            // Открываем страницу создания в НОВОЙ вкладке (только первый раз)
456            console.log('🔗 Открываем новую вкладку для создания заданий...');
457            await GM.openInTab(CONFIG.PAGES.CREATE, false);
458            
459            // Главная вкладка остаётся открытой и отслеживает прогресс
460            await this.monitorProgress();
461        }
462
463        // Отслеживание прогресса в главной вкладке
464        async monitorProgress() {
465            console.log('👀 Отслеживаем прогресс в главной вкладке...');
466            
467            while (!this.shouldStop) {
468                await delay(1000);
469                
470                // Проверяем результат
471                const result = await GM.getValue('ozon_task_result', null);
472                if (result) {
473                    const resultData = JSON.parse(result);
474                    
475                    if (resultData.success) {
476                        this.processedCount++;
477                        UI.updateProgress(this.processedCount, this.totalProducts, `Создано заданий: ${this.processedCount}`);
478                        console.log(`✅ Задание ${this.processedCount} создано успешно`);
479                        
480                        // Очищаем результат
481                        await GM.setValue('ozon_task_result', null);
482                        
483                        // Обновляем счётчик для рабочей вкладки
484                        await GM.setValue('ozon_current_task', JSON.stringify({
485                            settings: this.settings,
486                            processedCount: this.processedCount,
487                            shouldContinue: true
488                        }));
489                        
490                    } else if (resultData.noMoreProducts) {
491                        console.log('✅ Все товары обработаны');
492                        alert('Готово! Создано заданий: ' + this.processedCount);
493                        break;
494                    } else if (resultData.error) {
495                        console.error('❌ Ошибка:', resultData.error);
496                        alert('Ошибка: ' + resultData.error);
497                        break;
498                    }
499                }
500            }
501            
502            if (this.shouldStop) {
503                console.log('⏹️ Процесс остановлен пользователем');
504                // Сообщаем рабочей вкладке об остановке
505                await GM.setValue('ozon_current_task', JSON.stringify({
506                    settings: this.settings,
507                    processedCount: this.processedCount,
508                    shouldContinue: false
509                }));
510                alert('Процесс остановлен. Создано заданий: ' + this.processedCount);
511            }
512        }
513    }
514
515    // ============================================
516    // ЛОГИКА ДЛЯ СТРАНИЦЫ СОЗДАНИЯ ЗАДАНИЯ
517    // ============================================
518    
519    class TaskCreationHandler {
520        constructor(taskData) {
521            this.settings = taskData.settings;
522            this.processedCount = taskData.processedCount;
523        }
524
525        // Выполнить создание задания
526        async execute() {
527            console.log('📝 Начинаем создание задания на странице create...');
528
529            try {
530                // Ждём загрузки страницы
531                await delay(CONFIG.DELAYS.AFTER_PAGE_LOAD);
532
533                // Ждём появления кнопки "Добавить"
534                const addButton = await waitForElement(CONFIG.SELECTORS.CREATE_PAGE.ADD_PRODUCT_BUTTON, 15000);
535                if (!addButton) {
536                    throw new Error('Не найдена кнопка "Добавить"');
537                }
538
539                // Заполняем форму
540                await this.fillForm();
541
542                // Открываем модальное окно с товарами
543                await clickElement(addButton, 'Кнопка "Добавить товар"');
544                await delay(CONFIG.DELAYS.AFTER_CLICK);
545
546                // Выбираем товар
547                const productSelected = await this.selectProduct();
548                if (!productSelected) {
549                    // Все товары обработаны
550                    await GM.setValue('ozon_task_result', JSON.stringify({
551                        success: false,
552                        noMoreProducts: true
553                    }));
554                    // Переходим на страницу списка вместо закрытия
555                    window.location.href = CONFIG.PAGES.LIST;
556                    return;
557                }
558
559                // Сохраняем задание
560                await this.saveTask();
561
562                // Сообщаем об успехе
563                await GM.setValue('ozon_task_result', JSON.stringify({
564                    success: true
565                }));
566
567                console.log('✅ Задание создано, переходим на страницу списка...');
568                
569                // Переходим на страницу списка вместо закрытия вкладки
570                await delay(2000); // Ждём сохранения задания
571                window.location.href = CONFIG.PAGES.LIST;
572
573            } catch (error) {
574                console.error('❌ Ошибка при создании задания:', error);
575                await GM.setValue('ozon_task_result', JSON.stringify({
576                    success: false,
577                    error: error.message
578                }));
579                // Переходим на страницу списка
580                window.location.href = CONFIG.PAGES.LIST;
581            }
582        }
583
584        // Заполнить форму создания задания
585        async fillForm() {
586            console.log('📝 Заполнение формы...');
587
588            // Выбираем формат публикации
589            const postTypeSelector = this.settings.postType === 'TASK_POST_TYPE_CLIP' 
590                ? CONFIG.SELECTORS.CREATE_PAGE.POST_TYPE_CLIPS 
591                : CONFIG.SELECTORS.CREATE_PAGE.POST_TYPE_POSTS;
592            
593            const postTypeRadio = await waitForElement(postTypeSelector);
594            if (postTypeRadio && !postTypeRadio.checked) {
595                await clickElement(postTypeRadio, `Формат: ${this.settings.postType === 'TASK_POST_TYPE_CLIP' ? 'Клипы' : 'Посты'}`);
596            }
597
598            // Заполняем ставку
599            const bidInput = await waitForElement(CONFIG.SELECTORS.CREATE_PAGE.BID_INPUT);
600            if (bidInput) {
601                bidInput.value = this.settings.bid;
602                bidInput.dispatchEvent(new Event('input', { bubbles: true }));
603                bidInput.dispatchEvent(new Event('change', { bubbles: true }));
604                console.log(`💰 Ставка установлена: ${this.settings.bid}%`);
605            }
606
607            // Заполняем количество образцов
608            const samplesInput = await waitForElement(CONFIG.SELECTORS.CREATE_PAGE.SAMPLES_INPUT);
609            if (samplesInput) {
610                samplesInput.value = this.settings.samples;
611                samplesInput.dispatchEvent(new Event('input', { bubbles: true }));
612                samplesInput.dispatchEvent(new Event('change', { bubbles: true }));
613                console.log(`📦 Образцов установлено: ${this.settings.samples}`);
614            }
615
616            await delay(CONFIG.DELAYS.BETWEEN_ACTIONS);
617        }
618
619        // Выбрать товар из модального окна
620        async selectProduct() {
621            console.log('🔍 Поиск товара...');
622
623            // Ждём появления модального окна
624            const modal = await waitForElement(CONFIG.SELECTORS.MODAL.DIALOG, 10000);
625            if (!modal) {
626                console.error('❌ Модальное окно не открылось');
627                // Попробуем найти модальное окно другими способами
628                const allModals = document.querySelectorAll('[role="dialog"], .modal, [class*="modal"], [class*="Modal"]');
629                console.log(`🔍 Найдено потенциальных модальных окон: ${allModals.length}`);
630                if (allModals.length > 0) {
631                    console.log('📋 Классы найденных элементов:', Array.from(allModals).map(m => m.className));
632                }
633                return false;
634            }
635
636            await delay(1000);
637
638            // Ищем строки с товарами
639            const productRows = modal.querySelectorAll(CONFIG.SELECTORS.MODAL.PRODUCT_ROWS);
640            console.log(`📦 Найдено строк товаров: ${productRows.length}`);
641
642            for (const row of productRows) {
643                // Извлекаем SKU из строки
644                const sku = this.extractSkuFromRow(row);
645                if (!sku) continue;
646
647                // Проверяем, не обработан ли уже этот товар
648                const isProcessed = await Storage.isSkuProcessed(sku);
649                if (isProcessed) {
650                    console.log(`⏭️ SKU ${sku} уже обработан, пропускаем`);
651                    continue;
652                }
653
654                // Находим кнопку "Добавить" в этой строке
655                const addButton = row.querySelector(CONFIG.SELECTORS.MODAL.PRODUCT_ADD_BUTTON);
656                if (addButton) {
657                    await clickElement(addButton, `Добавление товара SKU: ${sku}`);
658                    await Storage.addProcessedSku(sku);
659                    return true;
660                }
661            }
662
663            // Если на текущей странице не нашли товар, переходим на следующую
664            // Ищем контейнер пагинации
665            const pagination = modal.querySelector(CONFIG.SELECTORS.MODAL.PAGINATION);
666            if (pagination) {
667                // Получаем все кнопки пагинации
668                const allButtons = pagination.querySelectorAll('button.t0c110-a1.table-500');
669                console.log(`🔍 Найдено кнопок пагинации: ${allButtons.length}`);
670                
671                if (allButtons.length > 0) {
672                    // Последняя кнопка - это стрелка "вперёд"
673                    const nextPageButton = allButtons[allButtons.length - 1];
674                    console.log(`🔍 Последняя кнопка: текст="${nextPageButton.textContent.trim()}", disabled=${nextPageButton.disabled}`);
675                    
676                    if (!nextPageButton.disabled) {
677                        console.log('➡️ Переход на следующую страницу товаров');
678                        await clickElement(nextPageButton, 'Следующая страница');
679                        await delay(CONFIG.DELAYS.AFTER_CLICK);
680                        return await this.selectProduct(); // Рекурсивно ищем на следующей странице
681                    } else {
682                        console.log('⚠️ Кнопка следующей страницы отключена - достигнут конец списка');
683                    }
684                }
685            }
686
687            console.log('❌ Не найдено необработанных товаров');
688            return false;
689        }
690
691        // Извлечь SKU из строки таблицы
692        extractSkuFromRow(row) {
693            // Ищем атрибут data-sku
694            const skuCell = row.querySelector('[data-sku]');
695            if (skuCell) {
696                return skuCell.getAttribute('data-sku');
697            }
698
699            // Альтернативный способ - поиск по тексту
700            const cells = row.querySelectorAll('td');
701            for (const cell of cells) {
702                const text = cell.textContent.trim();
703                // SKU обычно числовой
704                if (/^\d+$/.test(text) && text.length > 5) {
705                    return text;
706                }
707            }
708
709            return null;
710        }
711
712        // Сохранить задание
713        async saveTask() {
714            console.log('💾 Сохранение задания...');
715
716            // Ищем кнопку "Запустить" (не "Сохранить"!)
717            // Кнопка "Запустить" находится внизу формы
718            const buttons = document.querySelectorAll('button[type="submit"]');
719            console.log(`🔍 Найдено кнопок submit: ${buttons.length}`);
720            
721            // Ищем кнопку с текстом "Запустить"
722            let launchButton = null;
723            for (const button of buttons) {
724                const text = button.textContent.trim();
725                console.log(`📋 Текст кнопки: "${text}"`);
726                if (text.includes('Запустить') || text.includes('Создать')) {
727                    launchButton = button;
728                    break;
729                }
730            }
731
732            if (launchButton) {
733                console.log('🖱️ Клик: Запуск задания');
734                launchButton.click();
735                // НЕ закрываем вкладку сразу - ждём перехода на страницу списка
736                console.log('⏳ Ожидаем сохранения задания...');
737            } else {
738                console.error('❌ Не найдена кнопка "Запустить"');
739                throw new Error('Не найдена кнопка "Запустить"');
740            }
741        }
742    }
743
744    // ============================================
745    // ИНИЦИАЛИЗАЦИЯ
746    // ============================================
747    
748    async function init() {
749        console.log('🔧 Инициализация расширения...');
750
751        // Проверяем, на какой странице мы находимся
752        const currentUrl = window.location.href;
753        
754        if (currentUrl.includes('/app/advertisement/product/external-promo')) {
755            // Мы на странице списка заданий или создания
756            
757            if (currentUrl.includes('/external-promo/create')) {
758                // Страница создания - проверяем, есть ли задача для выполнения
759                const taskData = await GM.getValue('ozon_current_task', null);
760                if (taskData) {
761                    const task = JSON.parse(taskData);
762                    if (task.shouldContinue) {
763                        console.log('⚙️ Выполняем задачу создания задания...');
764                        const handler = new TaskCreationHandler(task);
765                        await handler.execute();
766                    }
767                }
768            } else {
769                // Страница списка - добавляем кнопку
770                await initListPage();
771            }
772        }
773    }
774
775    // Инициализация на странице списка
776    async function initListPage() {
777        console.log('📄 Инициализация страницы списка заданий');
778
779        // Проверяем, есть ли активная задача (вернулись после создания задания)
780        const taskData = await GM.getValue('ozon_current_task', null);
781        
782        if (taskData) {
783            const task = JSON.parse(taskData);
784            if (task.shouldContinue) {
785                console.log('🔄 Возврат после создания задания, продолжаем процесс...');
786                
787                // Показываем индикатор прогресса
788                const progressIndicator = document.createElement('div');
789                progressIndicator.style.cssText = `
790                    position: fixed;
791                    top: 20px;
792                    right: 20px;
793                    background: white;
794                    padding: 20px;
795                    border-radius: 8px;
796                    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
797                    z-index: 100000;
798                    min-width: 300px;
799                `;
800                progressIndicator.innerHTML = `
801                    <div style="margin-bottom: 10px; font-weight: 600; font-size: 16px;">
802                        ⚡ Массовое создание заданий
803                    </div>
804                    <div style="margin-bottom: 10px;">
805                        <strong>Создано заданий:</strong> <span id="ozon-task-count">${task.processedCount}</span>
806                    </div>
807                    <div style="margin-bottom: 15px; color: #666; font-size: 14px;">
808                        Обработка товаров...
809                    </div>
810                    <button id="ozon-stop-process-btn" style="
811                        width: 100%;
812                        padding: 10px;
813                        background: #ff4444;
814                        color: white;
815                        border: none;
816                        border-radius: 6px;
817                        cursor: pointer;
818                        font-weight: 600;
819                    ">
820                        ⏹️ Остановить процесс
821                    </button>
822                `;
823                document.body.appendChild(progressIndicator);
824                
825                // Обработчик кнопки остановки
826                document.getElementById('ozon-stop-process-btn').addEventListener('click', async () => {
827                    console.log('⏹️ Пользователь остановил процесс');
828                    await GM.setValue('ozon_current_task', null);
829                    await Storage.setRunning(false);
830                    alert('Процесс остановлен. Создано заданий: ' + task.processedCount);
831                    window.location.reload();
832                });
833                
834                // Проверяем результат последнего задания
835                const result = await GM.getValue('ozon_task_result', null);
836                if (result) {
837                    const resultData = JSON.parse(result);
838                    
839                    if (resultData.success) {
840                        task.processedCount++;
841                        console.log(`✅ Задание ${task.processedCount} создано успешно`);
842                        document.getElementById('ozon-task-count').textContent = task.processedCount;
843                    } else if (resultData.noMoreProducts) {
844                        console.log('✅ Все товары обработаны');
845                        await GM.setValue('ozon_current_task', null);
846                        await Storage.setRunning(false);
847                        progressIndicator.remove();
848                        alert('Готово! Создано заданий: ' + task.processedCount);
849                        return;
850                    } else {
851                        console.error('❌ Ошибка при создании задания:', resultData.error);
852                        await GM.setValue('ozon_current_task', null);
853                        await Storage.setRunning(false);
854                        progressIndicator.remove();
855                        alert('Ошибка: ' + resultData.error);
856                        return;
857                    }
858                    
859                    // Очищаем результат
860                    await GM.setValue('ozon_task_result', null);
861                }
862                
863                // Продолжаем процесс - создаём следующее задание
864                await delay(CONFIG.DELAYS.BETWEEN_ACTIONS);
865                
866                // Обновляем задачу с новым счётчиком
867                await GM.setValue('ozon_current_task', JSON.stringify({
868                    settings: task.settings,
869                    processedCount: task.processedCount,
870                    shouldContinue: true
871                }));
872                
873                console.log(`\n📄 Создание задания ${task.processedCount + 1}`);
874                console.log('🔗 Переходим на страницу создания задания...');
875                window.location.href = CONFIG.PAGES.CREATE;
876                return;
877            }
878        }
879
880        // Если нет активной задачи - очищаем старые данные
881        console.log('🧹 Очищаем старые данные задач');
882        await GM.setValue('ozon_current_task', null);
883
884        // Ждём появления кнопки "Создать задание" с увеличенным таймаутом
885        const createButton = await waitForElement(CONFIG.SELECTORS.LIST_PAGE.CREATE_BUTTON, 20000);
886        if (!createButton) {
887            console.error('❌ Не найдена кнопка "Создать задание"');
888            return;
889        }
890
891        // Проверяем, не добавлена ли уже наша кнопка
892        const existingButton = document.getElementById('ozon-bulk-create-btn');
893        if (existingButton) {
894            console.log('✅ Кнопка уже добавлена, пропускаем инициализацию');
895            return;
896        }
897
898        // Создаём и добавляем нашу кнопку
899        const bulkButton = UI.createBulkButton();
900        createButton.parentElement.appendChild(bulkButton);
901        console.log('✅ Кнопка "Массовое создание заданий" добавлена');
902
903        // Обработчик клика на кнопку
904        bulkButton.addEventListener('click', async () => {
905            console.log('🖱️ Клик на "Массовое создание заданий"');
906            
907            // Создаём и показываем модальное окно
908            const modal = UI.createSettingsModal();
909            UI.showModal(modal);
910
911            // Загружаем сохранённые настройки
912            const savedSettings = await Storage.getSettings();
913            if (savedSettings) {
914                document.getElementById('ozon-post-type').value = savedSettings.postType;
915                document.getElementById('ozon-bid').value = savedSettings.bid;
916                document.getElementById('ozon-samples').value = savedSettings.samples;
917            }
918
919            // Обработчик кнопки "Отмена"
920            document.getElementById('ozon-cancel-btn').addEventListener('click', () => {
921                UI.hideModal(modal);
922            });
923
924            // Обработчик кнопки "Сбросить список"
925            document.getElementById('ozon-reset-btn').addEventListener('click', async () => {
926                const confirmed = confirm('Вы уверены, что хотите сбросить список обработанных товаров? После этого можно будет создать задания для всех товаров заново.');
927                if (confirmed) {
928                    await GM.setValue(CONFIG.STORAGE_KEYS.PROCESSED_SKUS, '[]');
929                    console.log('🔄 Список обработанных товаров сброшен');
930                    alert('Список обработанных товаров успешно сброшен!');
931                }
932            });
933
934            // Обработчик кнопки "Запустить"
935            let taskCreator = null;
936            document.getElementById('ozon-start-btn').addEventListener('click', async () => {
937                // Получаем настройки из формы
938                const settings = {
939                    postType: document.getElementById('ozon-post-type').value,
940                    bid: document.getElementById('ozon-bid').value,
941                    samples: document.getElementById('ozon-samples').value
942                };
943
944                // Валидация
945                if (!settings.bid || settings.bid < 10) {
946                    alert('Пожалуйста, укажите ставку (минимум 10%)');
947                    return;
948                }
949
950                if (!settings.samples || settings.samples < 0) {
951                    alert('Пожалуйста, укажите количество образцов (минимум 0)');
952                    return;
953                }
954
955                // Сохраняем настройки
956                await Storage.saveSettings(settings);
957
958                // Показываем кнопку остановки
959                UI.showStopButton();
960
961                // Запускаем процесс
962                taskCreator = new BulkTaskCreator(settings);
963                await taskCreator.start();
964            });
965
966            // Обработчик кнопки "Остановить"
967            document.getElementById('ozon-stop-btn').addEventListener('click', () => {
968                if (taskCreator) {
969                    taskCreator.stop();
970                }
971            });
972
973            // Закрытие по клику вне модального окна
974            modal.addEventListener('click', (e) => {
975                if (e.target === modal) {
976                    UI.hideModal(modal);
977                }
978            });
979        });
980    }
981
982    // Запускаем инициализацию после загрузки страницы
983    if (document.readyState === 'loading') {
984        document.addEventListener('DOMContentLoaded', init);
985    } else {
986        init();
987    }
988
989})();