- 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.
287 lines
8.1 KiB
PHP
287 lines
8.1 KiB
PHP
<?php
|
|
namespace controls;
|
|
|
|
class Clients
|
|
{
|
|
static private function clients_has_deleted_column()
|
|
{
|
|
global $mdb;
|
|
|
|
static $has_deleted = null;
|
|
if ( $has_deleted !== null )
|
|
{
|
|
return (bool) $has_deleted;
|
|
}
|
|
|
|
try
|
|
{
|
|
$stmt = $mdb -> pdo -> prepare( 'SHOW COLUMNS FROM clients LIKE :column_name' );
|
|
if ( $stmt )
|
|
{
|
|
$stmt -> bindValue( ':column_name', 'deleted', \PDO::PARAM_STR );
|
|
$stmt -> execute();
|
|
$has_deleted = $stmt -> fetch( \PDO::FETCH_ASSOC ) ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
$has_deleted = 0;
|
|
}
|
|
}
|
|
catch ( \Throwable $e )
|
|
{
|
|
$has_deleted = 0;
|
|
}
|
|
|
|
return (bool) $has_deleted;
|
|
}
|
|
|
|
static private function sql_clients_not_deleted( $alias = '' )
|
|
{
|
|
$alias = trim( (string) $alias );
|
|
$prefix = $alias !== '' ? $alias . '.' : '';
|
|
|
|
if ( self::clients_has_deleted_column() )
|
|
{
|
|
return 'COALESCE(' . $prefix . 'deleted, 0) = 0';
|
|
}
|
|
|
|
return '1=1';
|
|
}
|
|
|
|
static public function main_view()
|
|
{
|
|
return \view\Clients::main_view(
|
|
\factory\Clients::get_all()
|
|
);
|
|
}
|
|
|
|
static public function save()
|
|
{
|
|
$id = \S::get( 'id' );
|
|
$name = trim( \S::get( 'name' ) );
|
|
$google_ads_customer_id = trim( \S::get( 'google_ads_customer_id' ) );
|
|
$google_merchant_account_id = trim( \S::get( 'google_merchant_account_id' ) );
|
|
$active_raw = \S::get( 'active' );
|
|
$active = (string) $active_raw === '0' ? 0 : 1;
|
|
|
|
if ( !$name )
|
|
{
|
|
\S::alert( 'Nazwa klienta jest wymagana.' );
|
|
header( 'Location: /clients' );
|
|
exit;
|
|
}
|
|
|
|
$google_ads_start_date = trim( \S::get( 'google_ads_start_date' ) );
|
|
|
|
$data = [
|
|
'name' => $name,
|
|
'google_ads_customer_id' => $google_ads_customer_id ?: null,
|
|
'google_merchant_account_id' => $google_merchant_account_id ?: null,
|
|
'google_ads_start_date' => $google_ads_start_date ?: null,
|
|
'active' => $active,
|
|
];
|
|
|
|
if ( $id )
|
|
{
|
|
\factory\Clients::update( $id, $data );
|
|
\S::alert( 'Klient został zaktualizowany.' );
|
|
}
|
|
else
|
|
{
|
|
\factory\Clients::create( $data );
|
|
\S::alert( 'Klient został dodany.' );
|
|
}
|
|
|
|
header( 'Location: /clients' );
|
|
exit;
|
|
}
|
|
|
|
static public function set_active()
|
|
{
|
|
$id = (int) \S::get( 'id' );
|
|
$active = (int) \S::get( 'active' ) === 1 ? 1 : 0;
|
|
|
|
if ( $id <= 0 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Brak ID klienta.' ] );
|
|
exit;
|
|
}
|
|
|
|
$client = \factory\Clients::get( $id );
|
|
if ( !$client )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono klienta.' ] );
|
|
exit;
|
|
}
|
|
|
|
if ( (int) ( $client['deleted'] ?? 0 ) === 1 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie mozna zmienic statusu usunietego klienta.' ] );
|
|
exit;
|
|
}
|
|
|
|
\factory\Clients::update( $id, [ 'active' => $active ] );
|
|
|
|
echo json_encode( [ 'success' => true, 'id' => $id, 'active' => $active ] );
|
|
exit;
|
|
}
|
|
|
|
static public function delete()
|
|
{
|
|
$id = \S::get( 'id' );
|
|
|
|
if ( $id )
|
|
{
|
|
\factory\Clients::delete( $id );
|
|
}
|
|
|
|
echo json_encode( [ 'success' => true ] );
|
|
exit;
|
|
}
|
|
|
|
static public function get()
|
|
{
|
|
$id = \S::get( 'id' );
|
|
$client = \factory\Clients::get( $id );
|
|
|
|
echo json_encode( $client ?: [] );
|
|
exit;
|
|
}
|
|
|
|
static public function sync_status()
|
|
{
|
|
global $mdb;
|
|
$clients_not_deleted_sql = self::sql_clients_not_deleted();
|
|
|
|
// Kampanie: 1 work unit per row (pending=0, done=1)
|
|
$campaigns_raw = $mdb->query(
|
|
"SELECT client_id, COUNT(*) as total,
|
|
SUM(CASE WHEN phase='done' THEN 1 ELSE 0 END) as done
|
|
FROM cron_sync_status WHERE pipeline='campaigns' GROUP BY client_id"
|
|
)->fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
// Produkty: 3 work units per row (pending=0, fetch=1, aggregate_30=2, done=3)
|
|
$products_raw = $mdb->query(
|
|
"SELECT client_id, COUNT(*) * 3 as total,
|
|
SUM(CASE phase WHEN 'fetch' THEN 1 WHEN 'aggregate_30' THEN 2 WHEN 'done' THEN 3 ELSE 0 END) as done
|
|
FROM cron_sync_status WHERE pipeline='products' GROUP BY client_id"
|
|
)->fetchAll( \PDO::FETCH_ASSOC );
|
|
|
|
$data = [];
|
|
|
|
foreach ( $campaigns_raw as $row )
|
|
{
|
|
$data[ $row['client_id'] ]['campaigns'] = [ (int) $row['done'], (int) $row['total'] ];
|
|
}
|
|
|
|
foreach ( $products_raw as $row )
|
|
{
|
|
$data[ $row['client_id'] ]['products'] = [ (int) $row['done'], (int) $row['total'] ];
|
|
}
|
|
|
|
// Walidacja Merchant (cron_campaigns_product_alerts_merchant) dziala na kursorze klienta.
|
|
// Pokazujemy postep per klient jako 0/1 albo 1/1 w aktualnym cyklu.
|
|
$merchant_cursor_client_id = (int) \services\GoogleAdsApi::get_setting( 'cron_campaigns_product_alerts_last_client_id' );
|
|
$merchant_clients_ids = $mdb -> query(
|
|
"SELECT id
|
|
FROM clients
|
|
WHERE " . $clients_not_deleted_sql . "
|
|
AND google_ads_customer_id IS NOT NULL
|
|
AND google_ads_customer_id <> ''
|
|
AND google_merchant_account_id IS NOT NULL
|
|
AND google_merchant_account_id <> ''
|
|
ORDER BY id ASC"
|
|
) -> fetchAll( \PDO::FETCH_COLUMN );
|
|
|
|
foreach ( (array) $merchant_clients_ids as $merchant_client_id )
|
|
{
|
|
$merchant_client_id = (int) $merchant_client_id;
|
|
if ( $merchant_client_id <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$done = ( $merchant_cursor_client_id > 0 && $merchant_client_id <= $merchant_cursor_client_id ) ? 1 : 0;
|
|
$data[ $merchant_client_id ]['merchant'] = [ $done, 1 ];
|
|
}
|
|
|
|
echo json_encode( [ 'status' => 'ok', 'data' => $data ] );
|
|
exit;
|
|
}
|
|
|
|
static public function force_sync()
|
|
{
|
|
global $mdb;
|
|
$clients_not_deleted_sql = self::sql_clients_not_deleted();
|
|
|
|
$id = (int) \S::get( 'id' );
|
|
$pipeline = trim( (string) \S::get( 'pipeline' ) );
|
|
|
|
if ( !$id )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Brak ID klienta.' ] );
|
|
exit;
|
|
}
|
|
|
|
$deleted_select = self::clients_has_deleted_column() ? 'COALESCE(deleted, 0) AS deleted' : '0 AS deleted';
|
|
$client = $mdb -> query(
|
|
"SELECT id, COALESCE(active, 0) AS active, " . $deleted_select . ", google_ads_customer_id, google_merchant_account_id
|
|
FROM clients
|
|
WHERE id = :id
|
|
LIMIT 1",
|
|
[ ':id' => $id ]
|
|
) -> fetch( \PDO::FETCH_ASSOC );
|
|
|
|
if ( !$client || (int) ( $client['deleted'] ?? 0 ) === 1 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono klienta.' ] );
|
|
exit;
|
|
}
|
|
|
|
if ( (int) ( $client['active'] ?? 0 ) !== 1 )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Klient jest nieaktywny. Aktywuj klienta przed synchronizacja.' ] );
|
|
exit;
|
|
}
|
|
|
|
if ( in_array( $pipeline, [ 'campaigns', 'products' ], true ) )
|
|
{
|
|
$where = [ 'client_id' => $id ];
|
|
$where['pipeline'] = $pipeline;
|
|
$mdb -> delete( 'cron_sync_status', $where );
|
|
}
|
|
else if ( $pipeline === 'campaigns_product_alerts_merchant' )
|
|
{
|
|
$has_ads_id = trim( (string) ( $client['google_ads_customer_id'] ?? '' ) ) !== '';
|
|
$has_merchant_id = trim( (string) ( $client['google_merchant_account_id'] ?? '' ) ) !== '';
|
|
if ( !$has_ads_id || !$has_merchant_id )
|
|
{
|
|
echo json_encode( [ 'success' => false, 'message' => 'Klient nie ma kompletnego Google Ads Customer ID i Merchant Account ID.' ] );
|
|
exit;
|
|
}
|
|
|
|
$previous_eligible_id = (int) $mdb -> query(
|
|
"SELECT MAX(id)
|
|
FROM clients
|
|
WHERE " . $clients_not_deleted_sql . "
|
|
AND google_ads_customer_id IS NOT NULL
|
|
AND google_ads_customer_id <> ''
|
|
AND google_merchant_account_id IS NOT NULL
|
|
AND google_merchant_account_id <> ''
|
|
AND id < :client_id",
|
|
[ ':client_id' => $id ]
|
|
) -> fetchColumn();
|
|
|
|
\services\GoogleAdsApi::set_setting( 'cron_campaigns_product_alerts_last_client_id', (string) max( 0, $previous_eligible_id ) );
|
|
}
|
|
else
|
|
{
|
|
// Domyslny reset (wszystkie pipeline oparte o cron_sync_status).
|
|
$mdb -> delete( 'cron_sync_status', [ 'client_id' => $id ] );
|
|
}
|
|
|
|
echo json_encode( [ 'success' => true, 'pipeline' => $pipeline ?: 'all' ] );
|
|
exit;
|
|
}
|
|
}
|