<?php
/**
 * Slice 8f-IssueOnApprove · IPC approve auto-submits sale into GOODS_CUSTOMER_INVOICE chain.
 *
 *  1) Approve an IPC → sale drafted AND submitted to chain (invoice_workflow_status=SUBMITTED)
 *  2) Chain step 1 (PC) → step 2 (PM) → step 3 (Accountant Ack, terminal)
 *  3) On terminal APPROVED, existing FinChainsController hook posts revenue JE + allocates
 *     sale_no + flips invoice_workflow_status='ISSUED' via Sales::allocateAndAck
 *  4) Now Receipts::record accepts a receipt for the sale (sale is ISSUED)
 *  5) arSummaryFor reads the receipt back through the IPC
 *  6) Idempotent approve: second call skips chain (already submitted)
 *  7) Existing IPCs verify still passes (backward compatibility)
 */
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__DIR__));
require_once ROOT . DS . 'config' . DS . 'config.php';
require_once ROOT . DS . 'core' . DS . 'Application.php';
require_once ROOT . DS . 'core' . DS . 'Model.php';
require_once ROOT . DS . 'core' . DS . 'DB.php';
$_SESSION = $_SESSION ?? [];
foreach ([
    'WorksIpcs.php','WorksBoq.php','WorksRetention.php','WorksProjectReport.php',
    'WorksCustomerRetention.php','Sales.php','Receipts.php','ApprovalChain.php','JournalPosting.php',
] as $m) {
    require_once ROOT . DS . 'app' . DS . 'models' . DS . $m;
}

function h(string $t): void { echo "\n==== $t ====\n"; }
function p(string $t): void { echo "  $t\n"; }
function ok(bool $c, string $m): void { echo ($c ? "  ✓ " : "  ✗ ") . $m . "\n"; }
function boom(string $why): void { echo "\nFATAL: $why\n"; exit(1); }

$db  = new DB();
$wi  = new WorksIpcs();
$wr  = new WorksRetention();
$rcp = new Receipts();
$ac  = new ApprovalChain();
$sm  = new Sales();

$pcId=100; $pcNm='Verify PC'; $pcG='Projects Coordinator';
$pmId=5;   $pmNm='Verify PM'; $pmG='Project Manager';
$aId =4;   $aNm ='Verify Acct'; $aG ='Accountant';
$fmId=3;   $fmNm='Verify FM';

/* ── 0. Anchor + prime + purge ─────────────────────────────────────── */
h('0. Anchor + prime + purge');
$anchor = $db->mySelect(
    "SELECT p.c0d, p.project_customer_id, p.crm_status
       FROM c_projects p JOIN works_boqs b ON b.project_id = p.c0d AND b.deleted = 0
      WHERE p.project_type='works' AND p.deleted=0 ORDER BY p.c0d ASC LIMIT 1",
    []
);
if (empty($anchor)) boom('No works project with BOQ.');
$prjId       = (int)$anchor[0]['c0d'];
$prevCustId  = (int)$anchor[0]['project_customer_id'];
$prevCrm     = (string)$anchor[0]['crm_status'];
$prevTerms   = $wr->termsFor($prjId) ?: ['pct' => 0.0, 'dlp_days' => 365];
$db->insertUpdate("UPDATE c_projects SET project_customer_id = 1, crm_status = 'APPROVED' WHERE c0d = :p", [':p' => $prjId]);
$wr->setTerms($prjId, 5.00, 90);

$oldIpcs = $db->mySelect("SELECT c0d, sale_id FROM works_ipcs WHERE project_id = :p AND notes LIKE 'IssueOnApproveVerify %'", [':p' => $prjId]) ?: [];
foreach ($oldIpcs as $oi) {
    $id  = (int)$oi['c0d']; $sid = (int)$oi['sale_id'];
    if ($sid > 0) {
        // Approval trail on the sale
        $sap = $db->mySelect("SELECT c0d FROM fin_approvals WHERE entity_type='sales' AND entity_id = :s", [':s' => $sid]) ?: [];
        foreach ($sap as $ap) {
            $db->insertUpdate("DELETE FROM fin_approval_log WHERE approval_id = :a", [':a' => (int)$ap['c0d']]);
            $db->insertUpdate("DELETE FROM fin_approvals    WHERE c0d          = :a", [':a' => (int)$ap['c0d']]);
        }
        // Journal entries posted by the ack hook
        $db->insertUpdate("DELETE FROM journal_lines WHERE journal_entry_id IN (SELECT c0d FROM journal_entries WHERE source_type='SALE' AND source_id=:s)", [':s' => $sid]);
        $db->insertUpdate("DELETE FROM journal_entries WHERE source_type='SALE' AND source_id = :s", [':s' => $sid]);
        // Receipts + sale
        $db->insertUpdate("DELETE FROM receipts    WHERE sale_id = :s", [':s' => $sid]);
        $db->insertUpdate("DELETE FROM sales_items WHERE sale_id = :s", [':s' => $sid]);
        $db->insertUpdate("DELETE FROM sales      WHERE c0d     = :s", [':s' => $sid]);
    }
    $db->insertUpdate("DELETE FROM works_ipc_lines WHERE ipc_id = :i", [':i' => $id]);
    $db->insertUpdate("DELETE FROM works_ipcs      WHERE c0d    = :i", [':i' => $id]);
}
$stray = $db->mySelect("SELECT c0d FROM works_ipcs WHERE project_id = :p AND status IN ('DRAFT','SUBMITTED')", [':p' => $prjId]) ?: [];
foreach ($stray as $s) {
    $db->insertUpdate("DELETE FROM works_ipc_lines WHERE ipc_id = :i", [':i' => (int)$s['c0d']]);
    $db->insertUpdate("DELETE FROM works_ipcs      WHERE c0d    = :i", [':i' => (int)$s['c0d']]);
}
p("Purged " . count($oldIpcs) . " prior IPCs + " . count($stray) . " stray drafts");

/* ── 1. Draft + certify + approve IPC → sale auto-submitted ────────── */
h('1. Approve IPC → sale drafted AND submitted to chain');
$dr = $wi->draft($prjId, ['notes' => 'IssueOnApproveVerify ' . date('YmdHis')], $pcId, $pcNm);
$ipcId = (int)$dr['id'];
$lines = $wi->linesFor($ipcId);
$targetLine = null; $targetQty = 0;
foreach ($lines as $l) {
    $r = (float)$l['contract_rate'];
    if ($r > 0 && (5000000 / $r) <= (float)$l['contract_qty']) {
        $targetLine = $l; $targetQty = round(5000000 / $r, 4); break;
    }
}
if (!$targetLine) boom('no BOQ line has capacity for 5M IPC');
$wi->setLineQty($ipcId, (int)$targetLine['c0d'], $targetQty);
$appRes = $wi->approve($ipcId, $pcId, $pcNm, $pcG);
if (!$appRes['ok']) boom('approve failed: ' . ($appRes['msg'] ?? '?'));
$saleId = (int)$appRes['sale_id'];
$aid    = (int)($appRes['approval_id'] ?? 0);
p("IPC {$dr['ipc_no']} · sale=$saleId · chain approval=$aid");
ok($aid > 0,                                                          'approval_id populated in response');
$sale = $db->mySelect("SELECT invoice_workflow_status, approval_id FROM sales WHERE c0d = :s", [':s' => $saleId]);
ok((string)$sale[0]['invoice_workflow_status'] === 'SUBMITTED',       'sale flipped to SUBMITTED');
ok((int)$sale[0]['approval_id'] === $aid,                             'sale.approval_id matches');

/* ── 2. Walk chain PC → PM → Acct ───────────────────────────────── */
h('2. Walk GOODS_CUSTOMER_INVOICE chain');
$ac->approve(['approval_id'=>$aid,'user_id'=>$pcId,'user_name'=>$pcNm,'user_group'=>$pcG,'comment'=>'PC ok']);
$ac->approve(['approval_id'=>$aid,'user_id'=>$pmId,'user_name'=>$pmNm,'user_group'=>$pmG,'comment'=>'PM ok']);
$ac->approve(['approval_id'=>$aid,'user_id'=>$aId, 'user_name'=>$aNm, 'user_group'=>$aG, 'comment'=>'Acct ack']);
$aRow = $ac->approvalById($aid);
ok((string)$aRow['status'] === 'APPROVED',                            'chain terminal APPROVED');
// Simulate FinChainsController::postDecisionHook for CLI verify (Accountant ack hook)
$jp   = new JournalPosting();
$jres = $jp->postForSaleAck($saleId);
$jeId = $jres['ok'] ? (int)($jres['id'] ?? 0) : 0;
$sm->allocateAndAck($saleId, $aId, $aNm, $jeId);
$sale2 = $db->mySelect("SELECT invoice_workflow_status, sale_no, journal_entry_id FROM sales WHERE c0d = :s", [':s' => $saleId]);
ok((string)$sale2[0]['invoice_workflow_status'] === 'ISSUED',          'sale flipped to ISSUED via allocateAndAck');
ok(!empty($sale2[0]['sale_no']),                                       'sale_no allocated');
ok((int)$sale2[0]['journal_entry_id'] > 0,                             'revenue JE stamped');

/* ── 3. Receipts::record now accepts the sale ───────────────────── */
h('3. Receipts::record works without manual sale flip');
$ipcFin  = $wi->findById($ipcId);
$invTotal = (float)$ipcFin['invoice_total'];
$r1 = $rcp->record([
    'sale_id' => $saleId, 'amount' => round($invTotal / 2, 2), 'currency' => 'TZS',
    'bank_account_id' => 1, 'payment_method' => 'BANK_TRANSFER',
    'bank_reference' => 'IOA-VERIFY-1', 'receipt_date' => date('Y-m-d'),
], $fmId, $fmNm);
ok($r1['ok'], 'first receipt recorded: ' . ($r1['msg'] ?? ''));
$ar = $wi->arSummaryFor($ipcId);
ok(abs($ar['received'] - round($invTotal / 2, 2)) < 0.01, 'IPC arSummary reflects the receipt');

/* ── 4. Idempotent approve ─────────────────────────────────────── */
h('4. Second approve is idempotent (skips chain re-submit)');
$again = $wi->approve($ipcId, $pcId, $pcNm, $pcG);
ok($again['ok'] && (int)$again['sale_id'] === $saleId, 'second approve returns same sale_id');

/* ── 5. Surface + wiring assertions ───────────────────────────── */
h('5. Model + wiring assertions');
$mBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'models' . DS . 'WorksIpcs.php');
ok(strpos($mBody, "GOODS_CUSTOMER_INVOICE") !== false, 'WorksIpcs::approve references GOODS_CUSTOMER_INVOICE');
ok(strpos($mBody, "markSubmitted")           !== false, 'WorksIpcs::approve calls Sales::markSubmitted');
ok(strpos($mBody, "'approval_id'")           !== false, 'WorksIpcs::approve returns approval_id key');
$fcBody = (string)file_get_contents(ROOT . DS . 'app' . DS . 'controllers' . DS . 'FinChainsController.php');
ok(strpos($fcBody, "'sales'")     !== false, "FinChainsController dispatches entity_type='sales'");
ok(strpos($fcBody, "allocateAndAck") !== false, 'FinChainsController calls Sales::allocateAndAck on terminal');

/* ── Z. Restore anchor state ────────────────────────────────── */
h('Z. Restore anchor state');
$sap = $db->mySelect("SELECT c0d FROM fin_approvals WHERE entity_type='sales' AND entity_id = :s", [':s' => $saleId]) ?: [];
foreach ($sap as $ap) {
    $db->insertUpdate("DELETE FROM fin_approval_log WHERE approval_id = :a", [':a' => (int)$ap['c0d']]);
    $db->insertUpdate("DELETE FROM fin_approvals    WHERE c0d          = :a", [':a' => (int)$ap['c0d']]);
}
$db->insertUpdate("DELETE FROM journal_lines WHERE journal_entry_id IN (SELECT c0d FROM journal_entries WHERE source_type='SALE' AND source_id=:s)", [':s' => $saleId]);
$db->insertUpdate("DELETE FROM journal_entries WHERE source_type='SALE' AND source_id = :s", [':s' => $saleId]);
$db->insertUpdate("DELETE FROM receipts    WHERE sale_id = :s", [':s' => $saleId]);
$db->insertUpdate("DELETE FROM sales_items WHERE sale_id = :s", [':s' => $saleId]);
$db->insertUpdate("DELETE FROM sales      WHERE c0d     = :s", [':s' => $saleId]);
$db->insertUpdate("DELETE FROM works_ipc_lines WHERE ipc_id = :i", [':i' => $ipcId]);
$db->insertUpdate("DELETE FROM works_ipcs      WHERE c0d    = :i", [':i' => $ipcId]);
$db->insertUpdate("UPDATE c_projects SET project_customer_id = :cid, crm_status = :cs WHERE c0d = :p",
    [':cid' => $prevCustId, ':cs' => $prevCrm, ':p' => $prjId]);
$wr->setTerms($prjId, (float)$prevTerms['pct'], (int)$prevTerms['dlp_days']);
p("Restored: customer_id=$prevCustId · crm=$prevCrm · retention_pct={$prevTerms['pct']}");

echo "\n=== END ===\n";
