Files
adsPRO/docs/memory.md
Jacek Pyziak b54a9a71b1 Add CLI script to fetch active Meta Ads insights for campaigns, adsets, and ads
- Implemented a new PHP script to retrieve insights for the last N days (default 30).
- Supports command-line options for token, account ID, days, API version, and output file.
- Fetches data at campaign, adset, and ad levels, with filtering for active statuses.
- Handles JSON output and optional file saving, including directory creation if necessary.
- Includes error handling for cURL requests and JSON responses.
2026-02-20 23:45:36 +01:00

427 lines
21 KiB
Markdown

# 2026-02-20 - Obsluga statusu ACTIVE dla klientow
## Zmienione pliki
- `autoload/controls/class.Clients.php`
- `save()` zapisuje teraz pole `active` (domyslnie `1`, gdy brak wartosci z formularza).
- Dodana nowa akcja `set_active()` pod endpoint `/clients/set_active` do szybkiej zmiany statusu klienta AJAX-em.
- `force_sync()` ma dodatkowa walidacje:
- nie pozwala kolejkowac synchronizacji dla klienta nieaktywnego (`active != 1`),
- nadal blokuje klienta usunietego (`deleted = 1`) i klienta bez wymaganych ID.
- Kompatybilnosc schematu `clients` bez kolumny `deleted`:
- helpery `clients_has_deleted_column()` i `sql_clients_not_deleted()`,
- `force_sync()` i `sync_status()` nie wywalaja sie, gdy w bazie nie ma kolumny `deleted`.
- `templates/clients/main_view.php`
- Tabela klientow ma nowa kolumne `Status` (Aktywny/Nieaktywny).
- Wiersz klienta trzyma `data-active` do obslugi UI i synchronizacji.
- Dodany przycisk toggle (ikona `fa-toggle-on/off`) do natychmiastowej aktywacji/dezaktywacji.
- Przyciski synchronizacji (kampanie/produkty/merchant) sa blokowane (`disabled`) dla nieaktywnego klienta i odblokowywane po aktywacji.
- Formularz Dodaj/Edytuj klienta ma nowe pole `Status klienta` (`active`).
- JS:
- `toggleClientActive()` wysyla POST na `/clients/set_active`,
- `updateClientStatusUI()` odswieza status i stan komorki Sync bez przeladowania strony,
- `loadSyncStatus()` pomija paski postepu dla nieaktywnych klientow i pokazuje `nieaktywny`.
## Gdzie to jest wykorzystywane
- Zarzadzanie statusem klienta:
- UI listy i formularza: `templates/clients/main_view.php`
- Backend zapisu i toggle: `autoload/controls/class.Clients.php`
- Ograniczenie recznego wymuszenia synchronizacji do klientow aktywnych:
- `autoload/controls/class.Clients.php` (`force_sync()`)
# 2026-02-20 - CRON kampanii (nowy przebieg, stare jako archiwum)
## Zmienione pliki
- `autoload/controls/class.Cron.php`
- Dodany nowy `cron_campaigns()` jako glowny endpoint pod nowy przeplyw.
- Stary kod zostal zachowany jako archiwum: `cron_campaigns_archive()`.
- Nowy przebieg:
- bierze tylko aktywnych klientow (`active = 1`) z Google Ads Customer ID,
- liczy okno dat na podstawie `google_ads_conversion_window_days` z `config.php` (z fallbackiem),
- konczy okno na `przedwczoraj` (bez pobierania danych dzisiejszych),
- przechodzi po datach dzien po dniu (rosnaco),
- zapisuje/aktualizuje kampanie do `campaigns`,
- zapisuje/aktualizuje historie dzienne do `campaigns_history` (upsert po `campaign_id + date_add`),
- zapisuje grupy reklam / groupy PMAX do `campaign_ad_groups`.
- po zakonczeniu kampanii + ad groups dla klienta, dla calego okna dat pobiera search terms dzienne do `campaign_search_terms_history`,
- po pobraniu historii search terms wykonuje agregacje do `campaign_search_terms` (zanim przejdzie do kolejnego klienta).
- Dodany krok syncu fraz dodanych i wykluczonych:
- tabele docelowe: `campaign_keywords` i `campaign_negative_keywords`,
- uruchamiany raz na cykl klienta (po ostatnim dniu okna), nie x razy dla kazdego dnia.
- Kampanie produktowe / PMAX:
- nie maja fraz dodanych, wiec w `campaign_keywords` moga miec 0 rekordow,
- frazy wykluczone sa dalej synchronizowane do `campaign_negative_keywords`.
# 2026-02-20 - Produkty: przygotowanie schematu bazy
## Zmienione pliki
- `migrations/016_products_model_unification.sql`
- Dodane kolumny produktowe bezposrednio do `products`:
- `custom_label_4`, `custom_label_3`, `title`, `description`, `google_product_category`, `product_url`.
- Backfill danych z `products_data` -> `products` (tylko gdy pole w `products` jest puste).
- Dodana nowa tabela agregacyjna `products_aggregate`:
- scope: `product_id + campaign_id + ad_group_id` (unikalne),
- metryki `*_30` i `*_all_time`,
- `date_sync` (kiedy agregat byl przeliczony).
- `docs/database.sql`
- Zaktualizowana definicja `products` o nowe kolumny danych produktu.
- Dodana definicja tabeli `products_aggregate`.
## Ustalenie projektowe
- `products` staje sie glowna tabela danych produktu.
- `products_data` zostaje tymczasowo dla kompatybilnosci starego kodu; dane sa migrowane do `products`.
- Agregaty dla widokow `/products` powinny docelowo byc czytane z `products_aggregate` zamiast liczenia w locie.
# 2026-02-20 - Produkty: przepiecie na `products` + agregaty
## Zmienione pliki
- `autoload/factory/class.Products.php`
- `get_product_data()`:
- najpierw czyta pola produktowe z `products` (`custom_label_4`, `custom_label_3`, `title`, `description`, `google_product_category`, `product_url`),
- fallback do `products_data` dla kompatybilnosci.
- `set_product_data()`:
- zapisuje pole glownie do `products`,
- rownolegle mirroruje zapis do `products_data` (kompatybilnosc starego kodu).
- `autoload/controls/class.Cron.php`
- `sync_products_fetch_for_client()`:
- import produktow zapisuje dane produktowe bezposrednio do `products` (w tym `title`, `product_url`),
- usuniete poleganie na `products_data` podczas samego fetchu.
- `aggregate_products_history_30_for_client()`:
- po przeliczeniu `products_history_30` odpala przebudowe agregatow `products_aggregate` dla klienta i dnia.
- Dodana metoda `rebuild_products_aggregate_for_client( $client_id, $date_sync )`:
- liczy metryki `*_30` i `*_all_time` z `products_history`,
- zapisuje scope (`product_id + campaign_id + ad_group_id`) do `products_aggregate`.
- `rebuild_products_temp_for_client()`:
- przestawione z liczenia bezposrednio po `products_history` na odczyt z `products_aggregate`,
- zmniejsza liczenie "w locie" dla widoku `/products`.
- `cron_product_history_30_save()`:
- `products_history_30` przechowuje teraz srednie dzienne wartosci z okna do 30 dni (zamiast sumy okna),
- nadal zapisuje `roas_all_time` dla danego dnia.
- `generate_custom_feed_for_client()`:
- zrodlo danych produktowych przepiete na `products` (bez wymaganego `INNER JOIN products_data`).
- diagnostyka i pobieranie brakujacych URL (`cron_products_urls`):
- logika "ma URL / brak URL" bierze pod uwage `products.product_url` z fallbackiem do `products_data`.
## Gdzie to jest wykorzystywane
- Pipeline produktowy:
- `/cron/cron_products`
- etap `fetch` -> `products_history`,
- etap agregacji -> `products_history_30` + `products_aggregate`,
- etap finalny -> `products_temp` budowane z `products_aggregate`.
- Widok tabeli produktow `/products`:
- dane nadal czytane z `products_temp`, ale `products_temp` jest teraz zasilane agregatami z `products_aggregate`.
- Dodany helper `sync_campaigns_snapshot_for_client()` dla nowego przebiegu kampanii.
- Dodany helper `sync_campaign_terms_backfill_for_client()` dla kroku fraz (history + agregacja).
- Tryb wykonania nowego pipeline kampanii: 1 dzien = 1 wywolanie CRON.
- Na jednym wywolaniu: kampanie + ad groups + search terms history + agregacja search terms dla jednego dnia.
- Kolejne wywolanie przechodzi do kolejnego dnia dla tego samego klienta.
- Tryb debug dla nowego CRON:
- `?debug=true` zwraca czytelny HTML (podsumowanie + pelny payload),
- bez debug zwracany jest standardowy JSON.
- Dodany helper `cleanup_pipeline_rows_outside_window()` aby pipeline kampanii trzymal tylko aktualne okno dat.
- Filtry klientow w nowym CRON kampanii sa odporne na stare dane (`NULL`): `COALESCE(active,0)`, `COALESCE(deleted,0)`, `TRIM(COALESCE(google_ads_customer_id,''))`.
- Dodana kompatybilnosc schematu `clients` bez kolumny `deleted`:
- helpery: `clients_has_column()`, `sql_clients_not_deleted()`, `sql_clients_deleted()`,
- nowy pipeline kampanii (`cron_campaigns`/`cron_universal`) nie wywala sie na bazie bez `deleted`.
- `get_conversion_window_days( $prefer_config = false )` uwzglednia teraz konfiguracje z `config.php`.
- `sync_campaign_ad_groups_for_client()` dostal parametr `as_of_date`.
- `autoload/services/class.GoogleAdsApi.php`
- `get_ad_groups_30_days()` wspiera teraz parametr `as_of_date` i zakres dat `[as_of_date-29, as_of_date]`.
- `get_ad_groups_all_time()` wspiera teraz parametr `as_of_date` (filtr `segments.date <= as_of_date` z fallbackiem).
## Gdzie to jest wykorzystywane
- Głowny CRON kampanii: `/cron/cron_campaigns` -> `\controls\Cron::cron_campaigns()`.
- Uniwersalny CRON pipeline (zalecany endpoint): `/cron/cron_universal` -> `\controls\Cron::cron_universal()` (aktualnie deleguje do kroku kampanii).
- Archiwalny CRON kampanii (stara logika): `/cron/cron_campaigns_archive`.
- Dane do wykresow/tabel kampanii pozostaja pobierane z `campaigns_history`.
# 2026-02-20 - CRON uniwersalny jako glowny endpoint (1 dzien na wywolanie)
## Zmienione pliki
- `autoload/controls/class.Cron.php`
- `cron_universal()` nie deleguje juz do `cron_campaigns()`.
- W jednym wywolaniu realizuje sekwencje:
- `kampanie` (snapshot + ad groups + search terms + agregacja),
- `produkty` (fetch + `products_history_30` + `products_aggregate` + `products_temp`).
- Tryb pracy pozostaje: `1 wywolanie = 1 klient + 1 dzien`.
- Status dnia jest zapisywany do `cron_sync_status` dla obu pipeline:
- `campaigns`,
- `products`.
- Gdy krok kampanii zwroci blad, krok produktow dla tego dnia jest pomijany (`products_sync_skipped_reason=campaigns_failed`).
## Gdzie to jest wykorzystywane
- Docelowy adres CRON:
- `/cron/cron_universal?debug=true`
- Stare endpointy (`/cron/cron_campaigns`, `/cron/cron_products`) pozostaja w kodzie, ale nie sa docelowa sciezka wykonywania.
# 2026-02-20 - Poprawka niezaleznosci pipeline w `cron_universal`
## Problem
- `campaigns` mialo juz 100% (`done`) i `cron_universal` konczyl wykonanie, mimo ze `products` mial jeszcze zalegle daty.
## Zmienione pliki
- `autoload/controls/class.Cron.php`
- `cron_universal()` wybiera teraz aktywnego klienta niezaleznie dla obu pipeline:
- `campaigns`,
- `products`.
- Zakonczenie "wszyscy przetworzeni" następuje dopiero, gdy **oba** pipeline nie maja juz aktywnych pozycji.
- Dodane osobne liczenie pozostalych dat:
- `campaigns_remaining_dates`,
- `products_remaining_dates`.
- Statusy `done/pending` sa zapisywane osobno dla kazdego pipeline; produkty nie sa juz blokowane przez sam fakt, ze kampanie sa skonczone globalnie.
- Ujednolicenie trybu `client_id`:
- kampanie i produkty wykonują sie niezaleznie (w tym samym wywolaniu), a bledy sa laczone tylko w odpowiedzi.
# 2026-02-20 - Usuniecie `products_data`
## Zmienione pliki
- `migrations/017_drop_products_data.sql`
- Dodana migracja usuwajaca tabele `products_data`.
- `autoload/factory/class.Products.php`
- `get_product_data()` czyta dane tylko z `products`.
- `set_product_data()` zapisuje dane tylko do `products`.
- `autoload/controls/class.Cron.php`
- diagnostyka URL i wybieranie produktow bez URL opiera sie juz tylko o `products.product_url`.
- `docs/database.sql`
- usunieta definicja tabeli `products_data`.
- `migrations/demo_data.sql`
- usuniete operacje `INSERT/DELETE` na `products_data`,
- etykiety demo (`custom_label_4`) sa ustawiane bezposrednio w `products`.
## Gdzie to jest wykorzystywane
- Dane produktowe (`title`, `description`, `google_product_category`, `custom_label_3`, `custom_label_4`, `product_url`) sa trzymane tylko w `products`.
# 2026-02-20 - Ostatni krok `cron_universal`: URL z Merchant + alerty brakow
## Zmienione pliki
- `autoload/controls/class.Cron.php`
- Dodany helper `sync_products_urls_and_alerts_for_client()`.
- Na koncu przebiegu `cron_universal` (zarowno tryb automatyczny, jak i `client_id`) wykonywany jest krok:
- pobranie URL produktow z Google Merchant Center dla produktow bez URL,
- zapis URL do `products.product_url`.
- Gdy `offer_id` nie istnieje w Merchant Center, tworzony/aktualizowany jest alert w `campaign_alerts`:
- `alert_type = products_missing_in_merchant_center`,
- scope techniczny: `campaign_external_id = 0`, `ad_group_external_id = 0`,
- `meta_json` zawiera m.in. listy `missing_offer_ids` i `missing_product_ids`.
- Gdy w danym dniu brak brakujacych produktow, dzienny alert tego typu jest czyszczony.
- Do odpowiedzi cron dodane pola diagnostyczne:
- `merchant_urls_checked`,
- `merchant_urls_updated`,
- `merchant_missing_in_mc_count`,
- `merchant_missing_offer_ids`.
- `cron_universal` ma dodatkowy fallback niezalezny od pipeline `campaigns/products`:
- gdy oba pipeline sa zakonczone, ale sa jeszcze produkty bez URL, uruchamia sam krok Merchant URL + alerty (`merchant_only=1`),
- dopiero brak takich produktow daje komunikat "Wszyscy aktywni klienci zostali przetworzeni...".
- Krok Merchant URL nie jest wykonywany dla kazdego dnia okna; dziala jako osobny etap po zakonczeniu `campaigns/products`.
- Do zapytan do GMC trafiaja tylko produkty z `products.product_url IS NULL` i `merchant_url_not_found = 0`.
- Na jedno wywolanie wykonywana jest jedna paczka sprawdzen (limit z `config.php`: `cron_products_urls_limit_per_client`, ustawiony na `100`).
- Produkty, ktorych GMC nie zwraca (brak URL), sa oznaczane:
- `products.merchant_url_not_found = 1`,
- `products.merchant_url_last_check = NOW()`,
- dzieki temu nie sa wysylane ponownie w nieskonczonosc.
- Alert `products_missing_in_merchant_center` jest liczony na podstawie calej aktualnej puli `merchant_url_not_found = 1` (nie tylko bieżącej paczki), wiec nie znika przy `checked_products = 0`.
- Alerty sa per produkt (1 alert = 1 produkt):
- dla kazdego produktu bez URL i z `merchant_url_not_found = 1` tworzony jest osobny wpis w `campaign_alerts`,
- tresc alertu zawiera nazwe produktu (fallback: `name`, dalej `offer_id`) i `offer_id`,
- technicznie: `campaign_external_id = products.id`, co stabilizuje unikalnosc wpisu.
- `migrations/018_products_merchant_url_flags.sql`
- Dodane kolumny w `products`:
- `merchant_url_not_found` (TINYINT, domyslnie 0),
- `merchant_url_last_check` (DATETIME).
- Normalizacja: puste/sztuczne `product_url` (`'', '0', '-', 'null'`) ustawiane na `NULL`.
- `autoload/factory/class.Products.php`
- Przy zapisie `product_url`:
- ustawiany jest `merchant_url_last_check`,
- dla poprawnego URL resetowane jest `merchant_url_not_found = 0`.
# 2026-02-20 - Alerty na stronie `/products` dla klient + kampania
## Zmienione pliki
- `autoload/factory/class.Products.php`
- `get_scope_alerts()` nie wymaga juz wybranej grupy reklam:
- minimalny scope: `client_id + campaign_id`,
- filtr `ad_group_id` jest stosowany tylko opcjonalnie (gdy grupa jest wybrana).
- `templates/products/main_view.php`
- `load_scope_alerts()` pobiera alerty juz dla kombinacji `klient + kampania`.
- Sekcja alertow ma zaktualizowany opis: kampania + opcjonalna grupa reklam.
## Gdzie to jest wykorzystywane
- `/products`
- Panel alertow pod filtrami pokazuje alerty:
- dla calej kampanii (gdy grupa reklam nie jest wybrana),
- lub zawezone do konkretnej grupy (gdy grupa reklam jest wybrana).
# 2026-02-20 - Etykietowanie alertow Merchant (bez falszywej kampanii)
## Zmienione pliki
- `autoload/controls/class.Cron.php`
- Dla alertu `products_missing_in_merchant_center` nie jest juz zapisywany `product_id` w `campaign_external_id`.
- Pola scope kampanii/grupy sa zapisywane jako `0` (alert produktowy, bez przypisania do kampanii).
- `templates/campaign_alerts/main_view.php`
- Dla alertu `products_missing_in_merchant_center` tabela alertow pokazuje:
- Kampania: `Produkt (Merchant Center)`,
- Grupa reklam: `---`.
- Dla pozostalych alertow fallback `Kampania #...` / `Grupa reklam #...` dziala tylko dla dodatnich external_id; dla `0` pokazuje neutralne etykiety.
# 2026-02-20 - Powiazanie `campaign_alerts` z `products`
## Zmienione pliki
- `migrations/019_campaign_alerts_product_id.sql`
- Dodana kolumna `campaign_alerts.product_id` (NULL) oraz indeks `idx_alert_product`.
- `autoload/controls/class.Cron.php`
- Alerty `products_missing_in_merchant_center` zapisuja `product_id` w tabeli `campaign_alerts`.
- Dla zachowania unikalnosci dziennej per produkt, techniczny `campaign_external_id` pozostaje rowny `product_id`.
- `autoload/factory/class.CampaignAlerts.php`
- `get_alerts()` zwraca teraz rowniez pole `product_id`.
- `docs/database.sql`
- Dodana aktualna definicja tabeli `campaign_alerts` z kolumna `product_id`.
# 2026-02-20 - CRON produktow: `title` nie jest uzupelniany automatycznie
## Zmienione pliki
- `autoload/controls/class.Cron.php`
- W syncu produktow do tabeli `products` CRON nie zapisuje juz pola `title`.
- Dla nowych produktow CRON zapisuje tylko `name` (bez `title`).
- Dla istniejacych produktow usunieto automatyczne uzupelnianie pustego `title`.
## Gdzie to jest wykorzystywane
- `/cron/cron_universal`
- automatyczny import produktow nie nadpisuje ani nie uzupelnia `products.title`,
- `title` pozostaje polem do recznej edycji i wysylki do GMC.
# 2026-02-20 - Lista produktow z 0 wyswietlen (30 dni) na `/products`
## Zmienione pliki
- `autoload/factory/class.Products.php`
- Dodana metoda `get_products_without_impressions_30( $client_id, $campaign_id, $limit )`.
- Zwraca produkty z wybranej kampanii, ktore maja sume `impressions_30 = 0` na podstawie `products_aggregate`.
- Dodatkowy filtr `ad_group_id` (opcjonalny), aby lista byla zgodna z aktualnym filtrem grupy reklam na widoku.
- `autoload/controls/class.Products.php`
- Dodany endpoint `get_products_without_impressions_30()`.
- Zwraca JSON: `status`, `products[]`, `count` i przyjmuje opcjonalnie `ad_group_id`.
- `templates/products/main_view.php`
- Dodana sekcja nad tabela produktow:
- "Produkty do sprawdzenia (0 wyswietlen w ostatnich 30 dniach)".
- Sekcja pojawia sie dla wybranego `klient + kampania`.
- Lista odswieza sie przy zmianie klienta/kampanii/grupy oraz po zaladowaniu strony.
## Gdzie to jest wykorzystywane
- `/products`
- pomocnicza lista produktow potencjalnie nieistniejacych / wymagajacych weryfikacji (0 wyswietlen w 30 dni dla wybranej kampanii).
# 2026-02-20 - Ustawienia CRON: poprawka licznika klientow + usuniecie "Krok 1/Krok 2"
## Zmienione pliki
- `autoload/controls/class.Users.php`
- Licznik `Klienci z Google Ads ID` liczy teraz klientow z:
- `COALESCE(active, 0) = 1`,
- `TRIM(COALESCE(google_ads_customer_id, '')) <> ''`.
- Analogicznie poprawione filtry dla klientow Merchant i zapytan pomocniczych (wg `active`).
- Harmonogram krokow (`Krok 1`, `Krok 2`) w danych dashboardu CRON jest pusty.
- `templates/users/settings.php`
- Usunieta sekcja wizualna harmonogramu krokow CRON (`Krok 1` / `Krok 2`).
- Usunieta obsluga renderowania tej sekcji w JS odswiezajacym status CRON.
## Gdzie to jest wykorzystywane
- `/settings?settings_tab=cron`
- licznik klientow z Google Ads ID pokazuje poprawna wartosc na podstawie aktywnych klientow (`active = 1`),
- brak sekcji "Krok 1 / Krok 2".
# 2026-02-20 - `/products` czyta bezposrednio z `products_aggregate`
## Zmienione pliki
- `autoload/factory/class.Products.php`
- Zapytania dla listy produktow i licznikow zostaly przepiete z `products_temp` na `products_aggregate`:
- `get_products()`,
- `get_roas_bounds()`,
- `get_records_total_products()`,
- `get_product_full_context()`.
- Metryki all-time sa liczone z pol:
- `impressions_all_time`, `clicks_all_time`, `cost_all_time`, `conversions_all_time`, `conversion_value_all_time`.
- Metryki 30d sa czytane z:
- `impressions_30`, `clicks_30`.
## Gdzie to jest wykorzystywane
- `/products`
- tabela i liczniki nie zaleza juz od `products_temp`; biora dane bezposrednio z `products_aggregate`.
# 2026-02-20 - `custom_label_4` tylko z tabeli `products`
## Ustalenie
- Etykieta `custom_label_4` jest czytana i zapisywana z tabeli `products`.
- Agregaty (`products_aggregate`) nie sa zrodlem dla pola `custom_label_4`.
# 2026-02-20 - Usuniecie funkcjonalnosci `bestseller_min_roas`
## Zmienione pliki
- `templates/products/main_view.php`
- Usuniety filtr UI: pole `Bestseller min ROAS` (`#bestseller_min_roas`).
- Usuniety frontendowy loader wartosci progu (`load_client_bestseller_min_roas`).
- Usuniete wywolania loadera przy zmianie klienta i przy inicjalizacji strony.
- Usuniety zapis AJAX progu klienta na blur (`/products/save_client_bestseller_min_roas/`).
- `autoload/controls/class.Products.php`
- Usuniete endpointy:
- `get_client_bestseller_min_roas()`
- `save_client_bestseller_min_roas()`
- `autoload/factory/class.Products.php`
- Usuniete metody dostepu do progu ROAS klienta:
- `get_client_bestseller_min_roas( $client_id )`
- `save_client_bestseller_min_roas( $client_id, $min_roas )`
- `autoload/controls/class.Cron.php`
- W `rebuild_products_temp_for_client()` usunieta logika automatycznej zmiany `custom_label_4` oparta o prog `bestseller_min_roas`.
- Funkcja pozostaje jako krok diagnostyczny zwracajacy liczbe scope z `products_aggregate`.
## Efekt
- Aplikacja nie odczytuje, nie zapisuje i nie wykorzystuje juz `bestseller_min_roas` w UI, endpointach ani w CRON.
- Automatyczne oznaczanie `custom_label_4 = bestseller` na podstawie tego progu zostalo wycofane.