PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC]); $db->exec("SET search_path TO admin,public"); $SN=['Register','Verify Email','DNS Setup','Domain Verify','Exchange Config','AntiSpam','Warmup','Live']; // === ACTIONS === $msg=''; if($_SERVER['REQUEST_METHOD']==='POST'){ $aid=(int)($_POST['aid']??0); $act=$_POST['act']??''; if($act==='advance'&&$aid){ $cur=(int)$db->query("SELECT current_step FROM office_accounts WHERE id=$aid")->fetchColumn(); if($cur<7){$db->exec("UPDATE office_accounts SET current_step=".($cur+1)." WHERE id=$aid");$msg="✅ Account #$aid → Step ".($cur+1).": ".$SN[$cur+1];} } if($act==='retreat'&&$aid){ $cur=(int)$db->query("SELECT current_step FROM office_accounts WHERE id=$aid")->fetchColumn(); if($cur>0){$db->exec("UPDATE office_accounts SET current_step=".($cur-1)." WHERE id=$aid");$msg="⬅ Account #$aid → Step ".($cur-1).": ".$SN[$cur-1];} } if($act==='setstatus'&&$aid){ $ns=preg_replace('/[^a-z]/','',strtolower($_POST['ns']??'')); if(in_array($ns,['active','warming','suspended','blocked','pending'])){ $db->exec("UPDATE office_accounts SET status='$ns' WHERE id=$aid");$msg="✅ Account #$aid → Status: $ns"; } } if($act==='batch_advance'){ $bs=(int)($_POST['bs']??-1); if($bs>=0&&$bs<7){ $n=$db->exec("UPDATE office_accounts SET current_step=".($bs+1)." WHERE current_step=$bs AND LOWER(status) IN ('active','warming','pending')"); $msg="✅ Batch: $n comptes Step $bs → Step ".($bs+1); } } if($act==='exchange_on'&&$aid){$db->exec("UPDATE office_accounts SET exchange_configured=true WHERE id=$aid");$msg="✅ Exchange activé #$aid";} if($act==='exchange_off'&&$aid){$db->exec("UPDATE office_accounts SET exchange_configured=false WHERE id=$aid");$msg="❌ Exchange désactivé #$aid";} // OPUS3-OFFICE-FACTORY -- Factory Create + Recovery + Batch Recovery if($act==='factory_create'){ $t=$_POST['tenant']??'';$c=(int)($_POST['count']??1);$p=$_POST['prefix']??'user'; if($t&&$c>0&&$c<=50){ $cr=0; for($i=0;$i<$c;$i++){ $nm=$p.rand(100,999); $db->exec("INSERT INTO office_accounts (name,tenant_domain,status,current_step,domains_count,has_license,exchange_configured) VALUES ('$nm','$t','pending',0,0,false,false)"); $cr++; } $msg="Factory: $cr comptes crees sur $t"; } else $msg="Factory: tenant requis, count 1-50"; } if($act==='recovery'&&$aid){ $db->exec("UPDATE office_accounts SET status='active',current_step=0 WHERE id=$aid"); $msg="Recovery #$aid: reset Active Step 0"; } if($act==='batch_recovery'){ $t=$_POST['tenant']??''; if($t){ $n2=$db->exec("UPDATE office_accounts SET status='active',current_step=0 WHERE tenant_domain='$t' AND LOWER(status) IN ('suspended','blocked')"); $msg="Batch Recovery: $n2 comptes reactives sur $t"; } } if($act==='add_graph_tenant'){ $tn=$_POST['tname']??'';$td=$_POST['tid']??'';$ci=$_POST['cid']??'';$cs2=$_POST['cs']??''; if($tn&&$td&&$ci&&$cs2){ $db->exec("INSERT INTO graph_tenants (tenant_domain,tenant_id,client_id,client_secret,status,daily_limit) VALUES ('$tn','$td','$ci','$cs2','active',10000) ON CONFLICT DO NOTHING"); $msg="Tenant $tn ajoute"; } } if(!isset($_GET['account_id']))header("Location:office-workflow.php".($msg?"?msg=".urlencode($msg):"")); } if(!$msg)$msg=$_GET['msg']??''; // === DRILL-DOWN MODE === $drill=(int)($_GET['account_id']??0); if($drill){ $acc=$db->query("SELECT * FROM office_accounts WHERE id=$drill")->fetch(); if(!$acc){echo"

Account #$drill not found

";exit;} $doms=$db->query("SELECT * FROM office_domains WHERE account_id=$drill ORDER BY domain_name")->fetchAll(); $tenant=$db->query("SELECT * FROM graph_tenants WHERE tenant_domain='".$acc['tenant_domain']."' LIMIT 1")->fetch(); ?> Account #<?=$drill?> — Office Workflow
← Back to Pipeline

🔍 Account #

Status
Current Step
/7 —
Exchange
✓ Configured':'✗ Not configured'?>
Domains

📊 Pipeline Progress

⚡ Actions

0):?>

🔄 Changer Status

'btn-gn','warming'=>'btn-am','suspended'=>'btn-rd','blocked'=>'btn-rd','pending'=>'btn-bl'] as $st=>$cls):if($st!==$sc):?>

🏢 Tenant:

Status
Users
Sends Today
Daily Limit

🌐 Domains ()

DomainStatus
query("SELECT COUNT(*) FROM office_accounts")->fetchColumn(); $_active=(int)$db->query("SELECT COUNT(*) FROM office_accounts WHERE LOWER(status)='active'")->fetchColumn(); $_warming=(int)$db->query("SELECT COUNT(*) FROM office_accounts WHERE LOWER(status)='warming'")->fetchColumn(); $_suspended=(int)$db->query("SELECT COUNT(*) FROM office_accounts WHERE LOWER(status) IN ('suspended','blocked')")->fetchColumn(); $_domains=(int)$db->query("SELECT COUNT(*) FROM office_domains")->fetchColumn(); $_dom_v=(int)$db->query("SELECT COUNT(*) FROM office_domains WHERE verification_status='Verified'")->fetchColumn(); $_tenants=(int)$db->query("SELECT COUNT(*) FROM graph_tenants")->fetchColumn(); // OPUS3-ALL-TENANTS $_all_tenants=(int)$db->query("SELECT COUNT(DISTINCT tenant_domain) FROM office_accounts WHERE tenant_domain IS NOT NULL AND tenant_domain != '' ")->fetchColumn(); $_tenant_list=$db->query("SELECT tenant_domain, COUNT(*) as accounts, SUM(domains_count) as total_domains, MAX(domains_count) as max_dom FROM office_accounts WHERE tenant_domain IS NOT NULL AND tenant_domain != '' GROUP BY tenant_domain ORDER BY accounts DESC LIMIT 100")->fetchAll(); $_sends=(int)$db->query("SELECT COUNT(*) FROM graph_send_log")->fetchColumn(); $_exchange=(int)$db->query("SELECT COUNT(*) FROM office_accounts WHERE exchange_configured=true")->fetchColumn(); $_by_step=$db->query("SELECT current_step,COUNT(*) as cnt FROM office_accounts GROUP BY current_step ORDER BY current_step")->fetchAll(); // Filters + pagination $pg=max(1,(int)($_GET['p']??1));$pp=50;$off=($pg-1)*$pp; $q=$_GET['q']??'';$sf=$_GET['sf']??'';$stf=$_GET['stf']??''; $w=[];$prm=[]; if($q){$w[]="(name ILIKE :q OR tenant_domain ILIKE :q)";$prm[':q']="%$q%";} if($sf!==''){$w[]="current_step=:sf";$prm[':sf']=(int)$sf;} if($stf){$w[]="LOWER(status)=:stf";$prm[':stf']=strtolower($stf);} $wh=$w?"WHERE ".implode(" AND ",$w):""; $st=$db->prepare("SELECT id,name,tenant_domain,LOWER(status) as status,current_step,exchange_configured,domains_count,has_license FROM office_accounts $wh ORDER BY id DESC LIMIT $pp OFFSET $off"); $st->execute($prm);$accs=$st->fetchAll(); $st2=$db->prepare("SELECT COUNT(*) FROM office_accounts $wh");$st2->execute($prm); $filt=(int)$st2->fetchColumn();$pages=max(1,ceil($filt/$pp)); $_top_tenants=$db->query("SELECT tenant_domain,status,users_count,sends_today,daily_limit FROM graph_tenants ORDER BY users_count DESC LIMIT 12")->fetchAll(); $_dom_list=$db->query("SELECT d.domain_name,d.verification_status,a.name as account_name FROM office_domains d LEFT JOIN office_accounts a ON d.account_id=a.id ORDER BY d.verification_status,d.domain_name LIMIT 50")->fetchAll(); ?> WEVADS — Office Workflow v2

🏢 Office Workflow v2

O365 accounts • Graph API tenants • Domains verification • Exchange pipeline — Click account ID to drill-down
Office Accounts
Active
Warming
Susp/Blocked
/
Domains Verified
Tenants (Office)
Graph Sends

📊 Account Pipeline — 8 Steps ( click Batch to advance all accounts at a step )

$sn):$cnt=0;foreach($_by_step as $s)if((int)$s['current_step']==$i)$cnt=(int)$s['cnt'];$clr=$i<3?'var(--am)':($i<6?'var(--cy)':'var(--gn)');?>
Step :
0):?>
🔍 Filtrer
Page / ( résultats)
Accounts
Tenants
Domains
Status
#NameTenantStatusStepExchangeDomainsLicenseActions
/7 Yes':'No'?> ✓':''?>
🔍
1):?>
$q,'sf'=>$sf,'stf'=>$stf])); for($i=max(1,$pg-3);$i<=min($pages,$pg+3);$i++):?>>

Graph API Tenants

tenants uniques dans office_accounts

TenantComptesDomainsMax/compte

Graph API Tenants (configurés)

0?round(($t['sends_today']??0)/($t['daily_limit'])*100):0;?>
TenantStatusUsersSent TodayDaily LimitUsage

Office Domains — Verified / Total

DomainStatusAccount

By Status

query("SELECT LOWER(status) as status,COUNT(*) as cnt FROM office_accounts GROUP BY LOWER(status) ORDER BY cnt DESC")->fetchAll(); foreach($by_st as $s):$pct=$_total>0?round($s['cnt']/$_total*100,1):0;$nc=$s['status']==='active'?'nt-g':($s['status']==='warming'?'nt-a':'nt-r');?>
StatusCountShare
%

Key Metrics

MetricValue
Total Accounts
Exchange Configured
Graph API Sends
Domains Verified
Domains Pending