feat: Enhance user settings with cron URL plan display

- Added a new field to display the cron URL plan in user settings.
- Updated JavaScript to handle the new plan data.

refactor: Unify product model and migrate data

- Migrated product data from `products_data` to `products` table.
- Added new columns to `products` for better data organization.
- Created `products_aggregate` table for storing aggregated product metrics.

chore: Drop deprecated products_data table

- Removed `products_data` table as data is now stored in `products`.

feat: Add merchant URL flags to products

- Introduced flags for tracking merchant URL status in `products` table.
- Normalized product URLs to handle empty or invalid values.

feat: Link campaign alerts to specific products

- Added `product_id` column to `campaign_alerts` table for better tracking.
- Created an index for efficient querying of alerts by product.

chore: Add debug scripts for client data inspection

- Created debug scripts to inspect client data from local and remote databases.
- Included error handling and output formatting for better readability.
This commit is contained in:
2026-02-20 17:50:14 +01:00
parent 0024a25bfb
commit 167ced3573
31 changed files with 5697 additions and 1227 deletions

View File

@@ -22,9 +22,14 @@
</div>
<div class="filter-group filter-group-ad-group">
<label for="products_ad_group_id"><i class="fa-solid fa-layer-group"></i> Grupa reklam</label>
<select id="products_ad_group_id" name="products_ad_group_id" class="form-control">
<option value="">- wszystkie grupy -</option>
</select>
<div class="ad-group-filter-actions">
<select id="products_ad_group_id" name="products_ad_group_id" class="form-control">
<option value="">- wszystkie grupy -</option>
</select>
<button type="button" id="delete-products-ad-group" class="btn-icon btn-icon-delete" title="Usun wybrana grupe reklam" disabled>
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
<div class="filter-group filter-group-roas">
<label for="bestseller_min_roas"><i class="fa-solid fa-star"></i> Bestseller min ROAS</label>
@@ -39,6 +44,26 @@
</div>
</div>
<details id="products_scope_alerts_box" class="products-scope-alerts hide">
<summary>
<i class="fa-solid fa-triangle-exclamation"></i>
Alerty dla wybranej kampanii (i opcjonalnie grupy reklam)
(<span id="products_scope_alerts_count">0</span>)
</summary>
<div class="products-scope-alerts-list" id="products_scope_alerts_list"></div>
</details>
<details id="products_zero_impressions_box" class="products-scope-alerts hide">
<summary>
<i class="fa-solid fa-magnifying-glass"></i>
Produkty do sprawdzenia (0 wyswietlen w ostatnich 30 dniach)
(<span id="products_zero_impressions_count">0</span>)
</summary>
<div class="products-scope-alerts-list">
<select id="products_zero_impressions_select" class="form-control" size="8"></select>
</div>
</details>
<!-- Akcje bulk -->
<div class="products-actions">
<button type="button" class="btn btn-danger btn-sm" id="delete-selected-products" disabled>
@@ -351,6 +376,151 @@ $( function()
products_table.ajax.reload( null, false );
}
function submit_delete_campaign_ad_group( campaign_id, ad_group_id, delete_scope )
{
function parse_json_loose( raw )
{
if ( typeof raw !== 'string' || !raw )
{
return null;
}
var text = $.trim( raw );
if ( !text )
{
return null;
}
try
{
return JSON.parse( text );
}
catch ( e )
{
var start = text.indexOf( '{' );
var end = text.lastIndexOf( '}' );
if ( start !== -1 && end > start )
{
try
{
return JSON.parse( text.substring( start, end + 1 ) );
}
catch ( e2 ) {}
}
}
return null;
}
function handle_success( message )
{
show_toast( message || 'Grupa reklam zostala usunieta.', 'success' );
localStorage.removeItem( 'products_ad_group_id' );
load_products_ad_groups( campaign_id, '' ).done( function() {
$.when( load_scope_alerts(), load_zero_impressions_products() ).always( function() {
reload_products_table();
} );
} );
}
function check_if_ad_group_still_exists()
{
var deferred = $.Deferred();
if ( !campaign_id || !ad_group_id )
{
deferred.resolve( true );
return deferred.promise();
}
$.ajax({
url: '/products/get_campaign_ad_groups/campaign_id=' + campaign_id,
type: 'GET',
dataType: 'json'
}).done( function( res ) {
var still_exists = false;
( res.ad_groups || [] ).forEach( function( row ) {
if ( String( row.id || '' ) === String( ad_group_id ) )
{
still_exists = true;
}
} );
deferred.resolve( still_exists );
}).fail( function() {
deferred.resolve( true );
} );
return deferred.promise();
}
var request_data = {
campaign_id: campaign_id,
ad_group_id: ad_group_id,
delete_scope: delete_scope
};
$.ajax({
url: '/products/delete_campaign_ad_group/',
type: 'POST',
data: request_data,
success: function( response )
{
var res = ( typeof response === 'object' && response !== null )
? response
: parse_json_loose( response );
if ( res && res.status === 'ok' )
{
handle_success( res.message );
}
else
{
check_if_ad_group_still_exists().done( function( still_exists ) {
if ( !still_exists )
{
handle_success( ( res && res.message ) ? res.message : 'Grupa reklam zostala usunieta.' );
return;
}
$.alert({
title: 'Blad',
content: ( res && res.message ) ? res.message : 'Nie udalo sie usunac grupy reklam.',
type: 'red'
});
} );
}
},
error: function( jqXHR )
{
var res = parse_json_loose( jqXHR && jqXHR.responseText ? jqXHR.responseText : '' );
if ( res && res.status === 'ok' )
{
handle_success( res.message );
return;
}
check_if_ad_group_still_exists().done( function( still_exists ) {
if ( !still_exists )
{
handle_success( ( res && res.message ) ? res.message : 'Grupa reklam zostala usunieta.' );
return;
}
$.alert({
title: 'Blad',
content: ( res && res.message ) ? res.message : 'Wystapil blad podczas usuwania grupy reklam.',
type: 'red'
});
} );
}
});
return false;
}
function load_client_bestseller_min_roas( client_id )
{
if ( !client_id )
@@ -386,16 +556,35 @@ $( function()
dataType: 'json'
}).done( function( res ) {
( res.campaigns || [] ).forEach( function( row ) {
$campaign.append( '<option value="' + row.id + '">' + row.campaign_name + '</option>' );
var channel_type = String( row.advertising_channel_type || '' ).toUpperCase();
$campaign.append(
'<option value="' + row.id + '" data-channel-type="' + channel_type + '">' + escape_html( row.campaign_name || '' ) + '</option>'
);
} );
if ( selected_campaign_id && $campaign.find( 'option[value="' + selected_campaign_id + '"]' ).length )
{
$campaign.val( selected_campaign_id );
}
update_delete_ad_group_button_state();
} );
}
function get_selected_products_campaign_channel_type()
{
var campaign_id = $( '#products_campaign_id' ).val() || '';
if ( !campaign_id )
{
return '';
}
return String(
$( '#products_campaign_id option[value="' + campaign_id + '"]' ).attr( 'data-channel-type' ) || ''
).toUpperCase();
}
function load_products_ad_groups( campaign_id, selected_ad_group_id )
{
var $ad_group = $( '#products_ad_group_id' );
@@ -403,6 +592,7 @@ $( function()
if ( !campaign_id )
{
update_delete_ad_group_button_state();
return $.Deferred().resolve().promise();
}
@@ -419,6 +609,168 @@ $( function()
{
$ad_group.val( selected_ad_group_id );
}
update_delete_ad_group_button_state();
} );
}
function update_delete_ad_group_button_state()
{
var campaign_channel_type = get_selected_products_campaign_channel_type();
var ad_group_id = $( '#products_ad_group_id' ).val() || '';
var is_shopping_campaign = campaign_channel_type === 'SHOPPING';
var can_delete = is_shopping_campaign && ad_group_id !== '';
var $btn = $( '#delete-products-ad-group' );
$btn.prop( 'disabled', !can_delete );
if ( !is_shopping_campaign )
{
$btn.attr( 'title', 'Opcja dostepna tylko dla kampanii produktowych (Shopping)' );
}
else if ( ad_group_id === '' )
{
$btn.attr( 'title', 'Wybierz grupe reklam do usuniecia' );
}
else
{
$btn.attr( 'title', 'Usun wybrana grupe reklam' );
}
}
function render_scope_alerts_box( alerts )
{
var $box = $( '#products_scope_alerts_box' );
var $count = $( '#products_scope_alerts_count' );
var $list = $( '#products_scope_alerts_list' );
var rows = Array.isArray( alerts ) ? alerts : [];
if ( !rows.length )
{
$count.text( '0' );
$list.empty();
$box.addClass( 'hide' ).removeAttr( 'open' );
return;
}
var html = '';
rows.forEach( function( row ) {
var date_text = row.date_detected || row.date_add || '';
var type_text = row.alert_type ? String( row.alert_type ) : '';
var message_text = row.message ? String( row.message ) : '';
html += ''
+ '<div class="products-scope-alert-item">'
+ '<div class="products-scope-alert-meta">'
+ '<span class="products-scope-alert-date">' + escape_html( date_text ) + '</span>'
+ ( type_text ? '<span class="products-scope-alert-type">' + escape_html( type_text ) + '</span>' : '' )
+ '</div>'
+ '<div class="products-scope-alert-message">' + escape_html( message_text ) + '</div>'
+ '</div>';
} );
$count.text( rows.length );
$list.html( html );
$box.removeClass( 'hide' ).attr( 'open', 'open' );
}
function load_scope_alerts()
{
var client_id = $( '#client_id' ).val() || '';
var campaign_id = $( '#products_campaign_id' ).val() || '';
var ad_group_id = $( '#products_ad_group_id' ).val() || '';
if ( !client_id || !campaign_id )
{
render_scope_alerts_box( [] );
return $.Deferred().resolve().promise();
}
return $.ajax({
url: '/products/get_scope_alerts/',
type: 'POST',
dataType: 'json',
data: {
client_id: client_id,
campaign_id: campaign_id,
ad_group_id: ad_group_id
}
}).done( function( res ) {
if ( res && res.status === 'ok' )
{
render_scope_alerts_box( res.alerts || [] );
}
else
{
render_scope_alerts_box( [] );
}
}).fail( function() {
render_scope_alerts_box( [] );
} );
}
function render_zero_impressions_box( products )
{
var $box = $( '#products_zero_impressions_box' );
var $count = $( '#products_zero_impressions_count' );
var $select = $( '#products_zero_impressions_select' );
var rows = Array.isArray( products ) ? products : [];
$select.empty();
if ( !rows.length )
{
$count.text( '0' );
$box.addClass( 'hide' ).removeAttr( 'open' );
return;
}
rows.forEach( function( row ) {
var product_id = parseInt( row.product_id || 0, 10 );
var offer_id = row.offer_id ? String( row.offer_id ) : '';
var product_name = row.name ? String( row.name ) : '';
var label = '#' + product_id + ( offer_id ? ' | ' + offer_id : '' ) + ' | ' + product_name;
$select.append( '<option value="' + product_id + '">' + escape_html( label ) + '</option>' );
} );
$count.text( rows.length );
$box.removeClass( 'hide' );
}
function load_zero_impressions_products()
{
var client_id = $( '#client_id' ).val() || '';
var campaign_id = $( '#products_campaign_id' ).val() || '';
var ad_group_id = $( '#products_ad_group_id' ).val() || '';
if ( !client_id || !campaign_id )
{
render_zero_impressions_box( [] );
return $.Deferred().resolve().promise();
}
return $.ajax({
url: '/products/get_products_without_impressions_30/',
type: 'POST',
dataType: 'json',
data: {
client_id: client_id,
campaign_id: campaign_id,
ad_group_id: ad_group_id
}
}).done( function( res ) {
if ( res && res.status === 'ok' )
{
render_zero_impressions_box( res.products || [] );
}
else
{
render_zero_impressions_box( [] );
}
}).fail( function() {
render_zero_impressions_box( [] );
} );
}
@@ -730,11 +1082,15 @@ $( function()
localStorage.setItem( 'products_client_id', client_id );
localStorage.removeItem( 'products_campaign_id' );
localStorage.removeItem( 'products_ad_group_id' );
update_delete_ad_group_button_state();
load_client_bestseller_min_roas( client_id );
load_products_campaigns( client_id, '' ).done( function() {
load_products_ad_groups( '', '' ).done( function() {
reload_products_table();
update_delete_ad_group_button_state();
$.when( load_scope_alerts(), load_zero_impressions_products() ).always( function() {
reload_products_table();
} );
} );
} );
});
@@ -744,9 +1100,12 @@ $( function()
var campaign_id = $( this ).val() || '';
localStorage.setItem( 'products_campaign_id', campaign_id );
localStorage.removeItem( 'products_ad_group_id' );
update_delete_ad_group_button_state();
load_products_ad_groups( campaign_id, '' ).done( function() {
reload_products_table();
$.when( load_scope_alerts(), load_zero_impressions_products() ).always( function() {
reload_products_table();
} );
} );
});
@@ -754,7 +1113,71 @@ $( function()
{
var ad_group_id = $( this ).val() || '';
localStorage.setItem( 'products_ad_group_id', ad_group_id );
reload_products_table();
update_delete_ad_group_button_state();
$.when( load_scope_alerts(), load_zero_impressions_products() ).always( function() {
reload_products_table();
} );
});
$( 'body' ).on( 'click', '#delete-products-ad-group', function( e )
{
e.preventDefault();
var campaign_id = $( '#products_campaign_id' ).val() || '';
var ad_group_id = $( '#products_ad_group_id' ).val() || '';
var campaign_channel_type = get_selected_products_campaign_channel_type();
if ( campaign_channel_type !== 'SHOPPING' )
{
$.alert({
title: 'Niedostepne',
content: 'Usuwanie grup reklam jest dostepne tylko dla kampanii produktowych (Shopping).',
type: 'orange'
});
return;
}
if ( !campaign_id || !ad_group_id )
{
$.alert({
title: 'Brak wyboru',
content: 'Wybierz kampanie i grupe reklam do usuniecia.',
type: 'orange'
});
return;
}
var ad_group_name = $.trim( $( '#products_ad_group_id option:selected' ).text() || '' );
var campaign_name = $.trim( $( '#products_campaign_id option:selected' ).text() || '' );
$.confirm({
title: 'Usuwanie grupy reklam',
content:
'Jak chcesz usunac grupe reklam <strong>' + escape_html( ad_group_name ) + '</strong> z kampanii <strong>' + escape_html( campaign_name ) + '</strong>?'
+ '<br><br><small>Opcja API usunie grupe reklam rowniez w Google Ads.</small>',
type: 'red',
buttons: {
local: {
text: 'Tylko lokalnie',
btnClass: 'btn-default',
action: function()
{
return submit_delete_campaign_ad_group( campaign_id, ad_group_id, 'local' );
}
},
google: {
text: 'Lokalnie + Google Ads',
btnClass: 'btn-red',
action: function()
{
return submit_delete_campaign_ad_group( campaign_id, ad_group_id, 'google' );
}
},
cancel: {
text: 'Anuluj'
}
}
});
});
var savedClient = localStorage.getItem( 'products_client_id' ) || '';
@@ -770,7 +1193,10 @@ $( function()
load_products_campaigns( $( '#client_id' ).val() || '', savedCampaign ).done( function() {
var selected_campaign_id = $( '#products_campaign_id' ).val() || '';
load_products_ad_groups( selected_campaign_id, savedAdGroup ).done( function() {
reload_products_table();
update_delete_ad_group_button_state();
$.when( load_scope_alerts(), load_zero_impressions_products() ).always( function() {
reload_products_table();
} );
} );
});