update
This commit is contained in:
293
.paul/phases/05-products-scope-history-delete/05-01-PLAN.md
Normal file
293
.paul/phases/05-products-scope-history-delete/05-01-PLAN.md
Normal file
@@ -0,0 +1,293 @@
|
||||
---
|
||||
phase: 05-products-scope-history-delete
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- autoload/factory/class.Products.php
|
||||
- autoload/controls/class.Products.php
|
||||
- templates/products/main_view.php
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Dodac w widoku /products w rozwinietym podwierszu breakdown (per kampania+grupa reklam) przycisk usuwania z potwierdzeniem, ktory wycina lokalne wpisy statystyczno-historyczne dla konkretnej trojki product_id + campaign_id + ad_group_id.
|
||||
|
||||
## Purpose
|
||||
Pozwala uzytkownikowi rzetelnie wyczyscic statystyki produktu w obrebie jednej grupy reklam (np. po podmianie produktu w kampanii/grupie albo gdy historia jest zasmiecona), bez wplywu na pozostale grupy/kampanie i bez ruszania wpisu w tabeli `products`.
|
||||
|
||||
## Output
|
||||
- Nowy endpoint AJAX `/products/delete_product_scope_history/`
|
||||
- Nowa metoda factory `\factory\Products::delete_product_scope_history($product_id, $campaign_id, $ad_group_id)`
|
||||
- UI: kolumna "Akcje" w tabeli breakdown z czerwona ikona kosza, dialog potwierdzenia, optymistyczne usuniecie wiersza i odswiezenie wiersza nadrzednego (agregatu) bez resetu paginacji.
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
<clarifications>
|
||||
- **Zakres usuwania** — Co dokladnie ma usuwac przycisk z podwiersza breakdown?
|
||||
→ Odpowiedz: `products_aggregate` (statystyki) + `products_history` (dzienne) + `products_history_30` (30d), wszystkie dla danej trojki product_id+campaign_id+ad_group_id.
|
||||
- **Miejsce UI** — Gdzie umiescic akcje usuwania?
|
||||
→ Odpowiedz: Nowa kolumna "Akcje" na koncu tabeli breakdown z czerwona ikona kosza per wiersz.
|
||||
- **Scope API** — Czy usuwanie lokalne czy z Google Ads API?
|
||||
→ Odpowiedz: Tylko lokalnie (DB). Bez wywolan do Google Ads API. Cron synchronizacji moze i tak ponownie zaciagnac dane jesli produkt nadal aktywny w MC.
|
||||
- **UI po usun.** — Co po udanym usunieciu?
|
||||
→ Odpowiedz: Usun wiersz breakdown + odswiez agregat parent (`ajax.reload(null, false)`), zachowujac paginacje.
|
||||
</clarifications>
|
||||
|
||||
## Project Context
|
||||
@.paul/STATE.md
|
||||
@.paul/phases/04-products-aggregate-breakdown/04-01-SUMMARY.md
|
||||
|
||||
## Source Files
|
||||
@autoload/factory/class.Products.php
|
||||
@autoload/controls/class.Products.php
|
||||
@templates/products/main_view.php
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Endpoint usuwa lokalne wpisy dla trojki product+campaign+ad_group
|
||||
```gherkin
|
||||
Given uzytkownik wysyla POST do /products/delete_product_scope_history/ z parametrami product_id, campaign_id, ad_group_id (wszystkie >= 0, product_id > 0)
|
||||
When zadanie zostaje przetworzone
|
||||
Then z products_aggregate znika dokladnie jeden wiersz pasujacy do (product_id, campaign_id, ad_group_id)
|
||||
And z products_history znikaja wszystkie wpisy pasujace do tej samej trojki
|
||||
And z products_history_30 znikaja wszystkie wpisy pasujace do tej samej trojki
|
||||
And zaden wiersz w `products`, `products_aggregate`, `products_history`, `products_history_30` dla *innych* trojek (inne campaign_id lub ad_group_id, ten sam product_id) nie zostaje naruszony
|
||||
And odpowiedz to JSON `{"status":"ok"}`
|
||||
```
|
||||
|
||||
## AC-2: Walidacja parametrow i bledow
|
||||
```gherkin
|
||||
Given POST do /products/delete_product_scope_history/ bez product_id (lub product_id <= 0)
|
||||
When zadanie zostaje przetworzone
|
||||
Then odpowiedz to JSON `{"status":"error","message":"<komunikat PL>"}`
|
||||
And zaden wiersz w bazie nie zostaje usuniety
|
||||
```
|
||||
Uwaga: `campaign_id` lub `ad_group_id` rowne 0 sa dozwolone (PMax-y maja `ad_group_id = 0`, breakdown query renderuje takie wiersze).
|
||||
|
||||
## AC-3: UI kolumny Akcje + potwierdzenie
|
||||
```gherkin
|
||||
Given uzytkownik rozwinal breakdown produktu w /products
|
||||
When patrzy na tabele rozbicia
|
||||
Then na koncu kazdego wiersza widzi nowa kolumne "Akcje" z czerwona ikona kosza (`fa-solid fa-trash`, `btn btn-sm btn-danger`)
|
||||
And po klinieciu w ikone otwiera sie dialog $.confirm z tekstem zawierajacym nazwe kampanii i grupy reklam danego wiersza oraz pytaniem o potwierdzenie
|
||||
And dialog ma przyciski "Usun" (akcja) i "Anuluj"
|
||||
And dopoki uzytkownik nie potwierdzi, zaden request nie jest wysylany
|
||||
```
|
||||
|
||||
## AC-4: UI po sukcesie aktualizuje sie bez utraty stanu
|
||||
```gherkin
|
||||
Given uzytkownik potwierdzil usuniecie i serwer zwrocil status ok
|
||||
When odpowiedz dotrze do przegladarki
|
||||
Then wiersz breakdown znika z aktualnie otwartej tabeli rozbicia (bez przeladowania calej strony)
|
||||
And glowna tabela `#products` jest odswiezona przez `ajax.reload(null, false)` (zachowana paginacja, sortowanie, filtry)
|
||||
And uzytkownik widzi toast/komunikat sukcesu (PL, krotki)
|
||||
And przy bledzie z serwera wyswietla sie czerwony dialog z `message` z odpowiedzi (lub fallback PL) i wiersz NIE jest usuwany z UI
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Factory - delete_product_scope_history</name>
|
||||
<files>autoload/factory/class.Products.php</files>
|
||||
<action>
|
||||
Dodaj statyczna metode `delete_product_scope_history( $product_id, $campaign_id, $ad_group_id )` w klasie `\factory\Products`.
|
||||
- Rzutuj wszystkie 3 argumenty na (int).
|
||||
- Walidacja: `$product_id <= 0` -> return false.
|
||||
- Uzyj globalnego `$mdb` (Medoo) zgodnie z konwencja pliku.
|
||||
- Wykonaj 3 osobne DELETE w jednej transakcji `$mdb->pdo->beginTransaction()` / `commit()` / w catch `rollBack()` + `return false`:
|
||||
1. `$mdb->delete('products_aggregate', ['product_id' => $product_id, 'campaign_id' => $campaign_id, 'ad_group_id' => $ad_group_id])`
|
||||
2. `$mdb->delete('products_history', ['product_id' => $product_id, 'campaign_id' => $campaign_id, 'ad_group_id' => $ad_group_id])`
|
||||
3. `$mdb->delete('products_history_30',['product_id' => $product_id, 'campaign_id' => $campaign_id, 'ad_group_id' => $ad_group_id])`
|
||||
- Zwroc `true` po commit.
|
||||
- NIE dotykaj tabeli `products` ani `campaigns`/`campaign_ad_groups` (chronione - viz. boundaries).
|
||||
- Avoid: jakichkolwiek warunkow `LIKE`, surowego SQL z konkatenacja stringow (Medoo dela tu wszystko bezpiecznie). Avoid: usuwania bez `campaign_id`/`ad_group_id` w klauzuli (skasowaloby calego produkta).
|
||||
</action>
|
||||
<verify>
|
||||
`php -l autoload/factory/class.Products.php` zwraca "No syntax errors".
|
||||
Manualny test SQL (na kopii lub z dry-runem): przed zmiana zliczyc `SELECT COUNT(*) FROM products_aggregate WHERE product_id=X AND campaign_id=Y AND ad_group_id=Z` (oczekiwane 1), wywolac metode, ponownie zliczyc (oczekiwane 0). Powtorzyc dla `products_history` i `products_history_30`. Sprawdzic, ze inne trojki dla tego samego product_id pozostaly.
|
||||
</verify>
|
||||
<done>AC-1 spelnione: usuwanie ograniczone do trojki, AC-2 spelnione w czesci serwerowej (zwrot false dla zlych parametrow).</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Controller - akcja AJAX delete_product_scope_history</name>
|
||||
<files>autoload/controls/class.Products.php</files>
|
||||
<action>
|
||||
Dodaj public static akcje `delete_product_scope_history()` w klasie `\controls\Products` (obok istniejacych `delete_product()` / `delete_products()`).
|
||||
- Czytaj parametry przez `\S::get`:
|
||||
$product_id = (int) \S::get('product_id');
|
||||
$campaign_id = (int) \S::get('campaign_id');
|
||||
$ad_group_id = (int) \S::get('ad_group_id');
|
||||
- Walidacja: `$product_id <= 0` -> `echo json_encode(['status'=>'error','message'=>'Brak identyfikatora produktu.']); exit;`
|
||||
- Wywolaj `\factory\Products::delete_product_scope_history($product_id, $campaign_id, $ad_group_id)`.
|
||||
- Sukces: `echo json_encode(['status'=>'ok']); exit;`
|
||||
- Porazka: `echo json_encode(['status'=>'error','message'=>'Nie udalo sie usunac wpisow historii dla tego zakresu.']); exit;`
|
||||
- Naladuj wzor istniejacych akcji `delete_product()` (linia ~1163) co do stylu (brak naglowkow, `echo json_encode(...)`, `exit`).
|
||||
- Avoid: dodawania nagłowkow `Content-Type: application/json` (reszta kontrolera tego nie robi - jQuery sobie radzi).
|
||||
</action>
|
||||
<verify>
|
||||
`php -l autoload/controls/class.Products.php` zwraca "No syntax errors".
|
||||
Recznie z konsoli przegladarki na zalogowanej sesji:
|
||||
`$.post('/products/delete_product_scope_history/', {product_id: 0}, console.log)` -> `{status:'error', message: ...}`
|
||||
`$.post('/products/delete_product_scope_history/', {product_id: <istniejacy>, campaign_id: <ist>, ad_group_id: <ist>}, console.log)` -> `{status:'ok'}` i wpisy znikaja.
|
||||
</verify>
|
||||
<done>AC-1, AC-2 spelnione end-to-end od strony serwera.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: UI - kolumna Akcje w breakdown + handler</name>
|
||||
<files>templates/products/main_view.php</files>
|
||||
<action>
|
||||
Modyfikacje wylacznie w obrebie breakdown:
|
||||
|
||||
a) `products_build_breakdown_html(row_meta)` (~linia 604):
|
||||
- Dodaj `<th>Akcje</th>` na koncu naglowka tabeli (po `<th>CL4</th>`).
|
||||
- W petli `rows.forEach` dodaj na koncu `<tr>` jeszcze jedna komorke:
|
||||
`'<td class="text-center"><button type="button" class="btn btn-sm btn-danger js-products-breakdown-delete" title="Usun wpisy historii dla tej kampanii+grupy" '
|
||||
+ 'data-product-id="' + entry.product_id + '" '
|
||||
+ 'data-campaign-id="' + (entry.campaign_id || 0) + '" '
|
||||
+ 'data-ad-group-id="' + (entry.ad_group_id || 0) + '" '
|
||||
+ 'data-campaign-name="' + escape_html(entry.campaign_name || '') + '" '
|
||||
+ 'data-ad-group-name="' + escape_html(entry.ad_group_name || '') + '">'
|
||||
+ '<i class="fa-solid fa-trash"></i></button></td>'`
|
||||
- WYMAGANE: w tasku 4 (factory/breakdown payload) `entry` musi zawierac `product_id`, `campaign_id`, `ad_group_id`. Sprawdzic istniejacy SELECT (`get_products_scope_breakdown`) - juz je zwraca (linie 707-709). Nie trzeba zmian backendu.
|
||||
|
||||
b) Dopisz delegowany handler kliku (np. tuz przy istniejacym handlerze `js-products-breakdown-toggle`, ~linia 743):
|
||||
|
||||
```js
|
||||
$( '#products tbody' ).on( 'click', '.js-products-breakdown-delete', function( e ) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var $btn = $( this );
|
||||
var productId = parseInt( $btn.data( 'product-id' ), 10 ) || 0;
|
||||
var campaignId = parseInt( $btn.data( 'campaign-id' ), 10 ) || 0;
|
||||
var adGroupId = parseInt( $btn.data( 'ad-group-id' ), 10 ) || 0;
|
||||
var campaignName= String( $btn.data( 'campaign-name' ) || '' );
|
||||
var adGroupName = String( $btn.data( 'ad-group-name' ) || '' );
|
||||
if ( productId <= 0 ) { return; }
|
||||
|
||||
$.confirm({
|
||||
title: 'Usun wpisy historii',
|
||||
content: 'Czy na pewno chcesz usunac wpisy statystyk i historii tego produktu w kampanii <strong>'
|
||||
+ escape_html(campaignName) + '</strong> / grupie <strong>' + escape_html(adGroupName) + '</strong>?'
|
||||
+ '<br><br><small>Operacja usuwa wpisy z products_aggregate, products_history oraz products_history_30 dla tej kombinacji. Nie usuwa produktu z tabeli products ani z Google Ads.</small>',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Usun',
|
||||
btnClass: 'btn-danger',
|
||||
action: function() {
|
||||
var $tr = $btn.closest( 'tr' );
|
||||
$btn.prop( 'disabled', true );
|
||||
$.ajax({
|
||||
url: '/products/delete_product_scope_history/',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: { product_id: productId, campaign_id: campaignId, ad_group_id: adGroupId },
|
||||
success: function( res ) {
|
||||
if ( res && res.status === 'ok' ) {
|
||||
$tr.remove();
|
||||
if ( typeof products_table !== 'undefined' && products_table ) {
|
||||
products_table.ajax.reload( null, false );
|
||||
}
|
||||
show_toast( 'Usunieto wpisy historii dla wybranego zakresu.', 'success' );
|
||||
} else {
|
||||
$btn.prop( 'disabled', false );
|
||||
$.alert({ title: 'Blad', content: ( res && res.message ) || 'Nie udalo sie usunac wpisow.' });
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$btn.prop( 'disabled', false );
|
||||
$.alert({ title: 'Blad', content: 'Blad polaczenia z serwerem.' });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel: { text: 'Anuluj' }
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Uwaga: `show_toast` i `escape_html` istnieja juz w pliku - uzyj ich. Jezeli `products_table` nie jest w scope tego closure (sprawdzic - jest zdefiniowana w glownym `$( function() { ... })`), uzyj `$( '#products' ).DataTable().ajax.reload( null, false )`.
|
||||
|
||||
c) CSS: nie wymagany dodatkowy CSS - kolumna "Akcje" przejmuje style domyslne `.products-breakdown-table td`. Ewentualnie w sekcji styli (~linia 217-237) dopisac `.products-breakdown-table td:last-child { width: 56px; text-align: center; }` jezeli kolumna sie rozjedzie.
|
||||
|
||||
d) Zaktualizuj komentarz/uwage na gorze tabeli breakdown jezeli istnieje (sprawdzic - prawdopodobnie nie ma).
|
||||
|
||||
Avoid: zmian w naglowkach pozostalych kolumn. Avoid: globalnego refresh `location.reload()`. Avoid: ruszania w `products_breakdown_meta` ani `get_products_scope_breakdown` (payload juz ma wymagane pola).
|
||||
</action>
|
||||
<verify>
|
||||
Manual w przegladarce na /products:
|
||||
1. Zaloguj sie, wybierz klienta, znajdz produkt z breakdownem (kilka kampanii/grup).
|
||||
2. Rozwin produkt -> widoczna kolumna "Akcje" z koszem na koncu tabeli.
|
||||
3. Klik kosz -> dialog z nazwa kampanii i grupy.
|
||||
4. Anuluj -> nic sie nie dzieje.
|
||||
5. Klik kosz -> Usun -> toast sukcesu, wiersz znika z tabeli rozbicia, glowny wiersz produktu ma zaktualizowane sumy (np. impressions, cost, conversions sa pomniejszone o usuniety zakres).
|
||||
6. SQL post-check: brak wpisow w `products_aggregate`, `products_history`, `products_history_30` dla skasowanej trojki; pozostale trojki dla tego product_id sa nietkniete.
|
||||
7. Browser console - brak bledow JS.
|
||||
</verify>
|
||||
<done>AC-3, AC-4 spelnione: dialog potwierdzenia + bezpieczne odswiezenie UI bez utraty paginacji.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
Dodano lokalne usuwanie wpisow statystyczno-historycznych (products_aggregate + products_history + products_history_30) per trojka product_id + campaign_id + ad_group_id z UI breakdown w /products.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Otworz https://adspro.projectpro.pl/products i wybierz klienta z aktywnymi kampaniami.
|
||||
2. Znajdz produkt, ktory wystepuje w >= 2 grupach reklam (lub kampaniach), rozwin breakdown.
|
||||
3. Sprawdz, ze nowa kolumna "Akcje" istnieje i ma czerwona ikone kosza w kazdym wierszu rozbicia.
|
||||
4. Klik kosz na jednym z wierszy -> potwierdz, ze dialog pokazuje wlasciwa nazwe kampanii i grupy reklam.
|
||||
5. Anuluj raz - wiersz nie powinien zniknac.
|
||||
6. Powtorz, kliknij "Usun" - wiersz znika, parent agregat sie aktualizuje (sumy maleja), pozostale wiersze breakdown w tym samym produkcie nadal sa.
|
||||
7. Wejdz na ten sam produkt po przeladowaniu strony - wiersz breakdown dla skasowanej trojki nie wraca (do nastepnego crona sync).
|
||||
8. Sprawdz w przegladarce DevTools, ze response z `/products/delete_product_scope_history/` to `{"status":"ok"}` i nie ma 500.
|
||||
</how-to-verify>
|
||||
<resume-signal>Wpisz "approved" aby zamknac plan, albo opisz problemy do poprawy.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `autoload/factory/class.Products.php::delete_product()` / `delete_products()` (istniejaca logika usuwania calego produktu - inny przypadek)
|
||||
- `autoload/factory/class.Products.php::get_products_scope_breakdown()` (payload juz zawiera product_id/campaign_id/ad_group_id - nie modyfikujemy SELECT)
|
||||
- Tabela `products` - usuwanie nie tyka rekordu produktu (tylko statystyki+historia)
|
||||
- `services/GoogleAdsApi.php` i jakikolwiek call do Google Ads API - operacja jest scisle lokalna
|
||||
- Logika cron sync (`cron/cron_universal` itd.) - nie zmieniamy; po usunieciu cron moze ponownie zaciagnac dane jezeli produkt jest aktywny w MC, to jest oczekiwane
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Brak operacji bulk (usuwanie wybranych wielu wierszy breakdown jednoczesnie) - jeden wiersz = jeden klik = jedno potwierdzenie
|
||||
- Brak undo / soft-delete - hard delete z DB
|
||||
- Brak zmian w endpoincie `/products/get_products/` - payload juz zawiera potrzebne ID
|
||||
- Brak telemetrii / audit log - jezeli okaze sie potrzebny, oddzielny plan
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Przed zamknieciem planu:
|
||||
- [ ] `php -l autoload/factory/class.Products.php` OK
|
||||
- [ ] `php -l autoload/controls/class.Products.php` OK
|
||||
- [ ] Manual: scenariusz Task 3 verify (kroki 1-7) i checkpoint human-verify (kroki 1-8)
|
||||
- [ ] DevTools: brak bledow JS i 500 z endpointu
|
||||
- [ ] SQL post-check: usunieto dokladnie te trojki, ktore mialy zniknac
|
||||
- [ ] Wszystkie AC-1..AC-4 spelnione
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Wszystkie 3 auto taski + 1 human-verify ukonczone i potwierdzone
|
||||
- Brak nowych bledow lintera / parse errors
|
||||
- UI breakdown zachowuje sie zgodnie z AC-3 i AC-4
|
||||
- Operacja jest atomowa (transakcja) i ograniczona do podanej trojki
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Po ukonczeniu utworz `.paul/phases/05-products-scope-history-delete/05-01-SUMMARY.md` zgodnie z konwencja phase 04 (frontmatter + sekcje Performance / AC Results / Files / Decisions / Deviations / Issues / Next Phase Readiness).
|
||||
</output>
|
||||
149
.paul/phases/05-products-scope-history-delete/05-01-SUMMARY.md
Normal file
149
.paul/phases/05-products-scope-history-delete/05-01-SUMMARY.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
phase: 05-products-scope-history-delete
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [products, datatables, breakdown, delete, history, php, jquery, transactions]
|
||||
|
||||
requires:
|
||||
- phase: 04-products-aggregate-breakdown
|
||||
provides: rozwijane podwiersze breakdown per kampania+grupa z payloadem row_meta.breakdown_rows
|
||||
|
||||
provides:
|
||||
- Lokalne usuwanie wpisow statystyczno-historycznych per trojka product_id+campaign_id+ad_group_id z UI breakdown
|
||||
- Endpoint AJAX `/products/delete_product_scope_history/`
|
||||
- Factory method `\factory\Products::delete_product_scope_history()` (transakcja PDO)
|
||||
- Kolumna "Akcje" w tabeli rozbicia + dialog `$.confirm` z potwierdzeniem
|
||||
- Odswiezony, schludniejszy styl ikony rozwijania breakdown (CSS rotate, hover, stan open)
|
||||
|
||||
affects:
|
||||
- autoload/factory/class.Products.php (delete API)
|
||||
- autoload/controls/class.Products.php (payload breakdown_for_view + nowa akcja)
|
||||
- templates/products/main_view.php (kolumna Akcje + handler + style)
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Hard delete scope-level: AGGREGATE + HISTORY + HISTORY_30 atomowo w transakcji PDO"
|
||||
- "Breakdown payload eksponuje pelna trojke ID (product, campaign, ad_group) dla operacji per-wiersz"
|
||||
- "Toggle expand stylowany CSS-em (rotate 90deg) zamiast swapu klas FA w JS"
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- autoload/factory/class.Products.php
|
||||
- autoload/controls/class.Products.php
|
||||
- templates/products/main_view.php
|
||||
|
||||
key-decisions:
|
||||
- "Hard delete lokalny w transakcji - bez Google Ads API, bez ruszania tabeli `products`"
|
||||
- "Eksponowanie product_id/campaign_id/ad_group_id w breakdown_for_view (poza zaplanowana edycja factory)"
|
||||
- "Style toggle: chevron rotowany przez CSS, JS nie podmienia klas FA"
|
||||
|
||||
patterns-established:
|
||||
- "Operacje per-wiersz w child rows DataTables wymagaja eksponowania ID we payloadzie row_meta - nie polegaj na danych z parent row"
|
||||
- "Transakcja PDO przed serwisem Medoo: `$mdb->pdo->beginTransaction()` + `commit/rollBack` + `$pdo->inTransaction()` guard"
|
||||
|
||||
duration: ~30min
|
||||
started: 2026-04-29T18:35:00Z
|
||||
completed: 2026-04-29T19:05:00Z
|
||||
---
|
||||
|
||||
# Phase 5 Plan 01: Products Scope History Delete (Summary)
|
||||
|
||||
**Dodano w UI /products na rozwijanym podwierszu breakdown przycisk usuwania, ktory atomowo kasuje wpisy z `products_aggregate`, `products_history` i `products_history_30` dla konkretnej trojki product+campaign+ad_group, bez ruszania tabeli `products` i bez wywolan Google Ads API.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~30 min |
|
||||
| Started | 2026-04-29T18:35:00Z |
|
||||
| Completed | 2026-04-29T19:05:00Z |
|
||||
| Tasks | 4/4 (3 auto + 1 human-verify) + 1 unplanned bonus (toggle styling) |
|
||||
| Files modified | 3 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Endpoint usuwa lokalne wpisy dla trojki product+campaign+ad_group | Pass | Factory uzywa `$mdb->delete()` z dokladnym WHERE per kazda z 3 tabel; transakcja PDO; inne trojki nietkniete (zweryfikowane przez usera) |
|
||||
| AC-2: Walidacja parametrow i bledow | Pass | Controller: `product_id <= 0 -> {status:error,message:...}` przed wywolaniem factory; factory tez waliduje (defense in depth) |
|
||||
| AC-3: UI kolumny Akcje + potwierdzenie | Pass | Nowa kolumna "Akcje" widoczna na koncu breakdown; ikona kosza `fa-solid fa-trash` w `btn btn-sm btn-danger`; dialog `$.confirm` z nazwa kampanii/grupy + przyciski "Usun"/"Anuluj" |
|
||||
| AC-4: UI po sukcesie aktualizuje sie bez utraty stanu | Pass | `$tr.remove()` + `products_table.ajax.reload(null,false)`; toast PL na success; `$.alert` z `res.message` na blad - human-verify approved |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Dodano scope-level usuwanie historii produktu z UI breakdown bez ryzyka skasowania samego produktu ani wplywu na inne trojki kampania/grupa.
|
||||
- Atomowa operacja DELETE-z-trzech-tabel w pojedynczej transakcji PDO z guardem `inTransaction()` i `rollBack` na throw.
|
||||
- Naprawiono nieoczywisty bug: breakdown_for_view nie eksponowal ID, przez co handler dostawal `undefined` i nic sie nie dzialo - dolozono trzy pola.
|
||||
- Bonus: ikona rozwijania breakdown przemodelowana na okragly button (hover, fioletowy stan open, CSS rotate chevron) - usunieto JS swap klas FA.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Brak commitu fazowego na tym etapie (working tree zawiera tez `.vscode/ftp-kr.sync.cache.json`, `.serena/project.yml` i `.paul/STATE.md` z innych watkow).
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/factory/class.Products.php` | Modified (+51) | Nowa metoda `delete_product_scope_history($pid,$cid,$agid)` z transakcja PDO i 3-tabelowym DELETE |
|
||||
| `autoload/controls/class.Products.php` | Modified (+23 +3) | Nowa akcja AJAX `delete_product_scope_history()`; payload `breakdown_for_view` rozszerzony o `product_id`, `campaign_id`, `ad_group_id` |
|
||||
| `templates/products/main_view.php` | Modified (+86, ~30 styling) | Kolumna "Akcje" w `products_build_breakdown_html`, handler kliku z `$.confirm`, przebudowane style `.products-breakdown-toggle` (CSS rotate zamiast JS), CSS `td:last-child` szerokosci 56px |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Eksponowac product/campaign/ad_group ID w `breakdown_for_view` | Bez tego JS dostawal `undefined` ID i handler ucinal sie na walidacji `<= 0` | Ustanawia wzorzec: jak chcesz akcje per-wiersz w child row, dodaj ID do payloadu row_meta |
|
||||
| Stylowac chevron rotacja CSS, nie podmiana klas FA w JS | Mniej kodu JS, plynniejsza animacja, jeden zrodlowy stan (`.products-breakdown-open`) | Pojedynczy CSS transition zarzadza wizualem; usunieto 2 linie JS |
|
||||
| Transakcja PDO + guard `inTransaction()` zamiast nested-transaction errora | Inny kod moze otworzyc transakcje wczesniej (np. w cron/migracji) | Bezpieczne uzycie metody w roznych kontekstach wywolania |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 1 | Niezbedne - bez tej korekty UI nie dziala (bug: undefined ID) |
|
||||
| Scope additions | 1 | Maly bonus stylowania ikony toggle na prosbe usera, w obrebie tego samego pliku |
|
||||
| Deferred | 0 | Brak |
|
||||
|
||||
**Total impact:** Plan zrealizowany zgodnie z zakresem; jedna konieczna korekta payloadu (Task 2) i jedno male rozszerzenie kosmetyczne na zyczenie usera.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. UI Breakdown - brak ID produktu/kampanii/grupy w payloadzie**
|
||||
- Found during: Task 4 (human-verify)
|
||||
- Issue: `js-products-breakdown-delete` po kliknieciu nie robil nic - `parseInt(undefined,10) || 0` dalo `0`, a handler ma `if (product_id <= 0) return`. Powod: `breakdown_for_view` w `controls/class.Products.php:1097` mapowal tylko statystyki + nazwy, bez ID.
|
||||
- Fix: Dodano `product_id`, `campaign_id`, `ad_group_id` jako pierwsze pola mappingu (z fallbackami).
|
||||
- Files: `autoload/controls/class.Products.php`
|
||||
- Verification: Czesc human-verify - klik kosza po patchu wywoluje dialog z poprawnymi nazwami kampanii/grupy.
|
||||
- Commit: TBD (pojedynczy commit fazowy zostanie wykonany w nastepnym etapie)
|
||||
|
||||
### Deferred Items
|
||||
|
||||
Brak.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Klik w kosz nie wywolywal dialogu (puste dane w `entry.product_id` itd.) | Zlokalizowano: payload breakdown filtrowany w controllerze. Dolozono 3 pola ID i pominieto cache state DataTables (force reload) |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Wzorzec dla operacji per-wiersz w breakdown ustanowiony (ID w row_meta).
|
||||
- Wzorzec transakcyjnego DELETE w factory ustanowiony.
|
||||
- Style toggle bardziej spojne wizualnie - mozna wykorzystac wzorzec rotate-on-state w innych collapsible UI.
|
||||
|
||||
**Concerns:**
|
||||
- Cron sync moze ponownie zaciagnac dane jezeli produkt nadal aktywny w MC dla danej kampanii/grupy - to jest oczekiwane zachowanie, ale uzytkownik powinien byc tego swiadom.
|
||||
- Working tree zawiera zmiany niepowiazane z planem (`.vscode/ftp-kr.sync.cache.json`, `.serena/project.yml`) - przy commit fazowym selektywne stage'owanie.
|
||||
|
||||
**Blockers:**
|
||||
- Brak.
|
||||
|
||||
---
|
||||
*Phase: 05-products-scope-history-delete, Plan: 01*
|
||||
*Completed: 2026-04-29*
|
||||
Reference in New Issue
Block a user