<?php
/**
 * Slice 8b-4a · Service Requisition foundation e2e.
 *
 *   1) Provision a services project.
 *   2) Try to submit an empty requisition → BLOCKED.
 *   3) Draft a requisition; add lines across 4 categories → total rolls up.
 *   4) Delete one line → total recomputes.
 *   5) Category guard: invalid category → BLOCKED. Qty ≤ 0 → BLOCKED.
 *   6) Submit → chain in progress; verify status SUBMITTED + approval_id.
 *   7) Walk PC → PM → FM → Exec 4-step chain → onChainDecision → APPROVED.
 *   8) Cancel path: fresh draft → cancel → CANCELLED (guard blocks cancel on SUBMITTED).
 *   9) linesByCategory: returns keyed structure with per-category totals.
 */
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 ([
    'ApprovalChain.php','Tenders.php','TenderPricing.php','ProjectStarter.php','Notifications.php',
    'ServiceRequisitions.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) { echo "\nFATAL: $why\n"; exit(1); }

$db = new DB();
$ac = new ApprovalChain();
$tm = new Tenders();
$sr = new ServiceRequisitions();

$uidCrm = 100; $unmCrm = 'CRM Test User';       $ugCrm  = 'CRM';
$uidFm  = 3;   $unmFm  = 'Finance Manager Test'; $ugFm  = 'Finance Manager';
$uidExec= 2;   $unmExec= 'Executive Test';      $ugExec = 'Executive';
$accId  = 4;   $accNm  = 'Accountant Test';
$procId = 8;   $procNm = 'Procurement Test';
$pmId   = 5;   $pmNm   = 'Project Manager Test';
$pcRow  = $db->mySelect("SELECT c0d, full_name FROM users WHERE user_group_name='Projects Coordinator' AND status='ACTIVE' AND deleted=0 ORDER BY c0d LIMIT 1", []);
$pcId   = (int)($pcRow[0]['c0d'] ?? 0);
$pcNm   = (string)($pcRow[0]['full_name'] ?? '');

$stamp = date('YmdHis');

/* ── 1 ── Provision services project ──────────────────────────────── */
h('1. Provision services project');
$fb = $db->insertUpdate(
    "INSERT INTO tender_tracker
        (tender_no, tender_title, source_type, buyer_name, project_type,
         date_received, estimated_value, currency, tender_stage, tender_status,
         created_by, created_by_name, created_on)
     VALUES (:tn, :tt, 'PRIVATE_CLIENT', 'Vodacom TZ', 'services',
         CURDATE(), 30000000, 'TZS', 'PREP', 'OPEN', :u, :un, NOW())",
    [':tn' => 'T-8B4A-' . $stamp, ':tt' => 'SLICE 8b-4a requisition verify', ':u' => $uidCrm, ':un' => $unmCrm]
);
$tid = (int)($fb['last_id'] ?? 0);
foreach (['SUBMISSION','FOLLOW_UP','OPENING'] as $s) $tm->advanceStage($tid, $s, 'x', $uidCrm, $unmCrm);
$tm->recordResult($tid, 'WON', date('Y-m-d'), '', 30000000, '', '', $uidCrm, $unmCrm);
$tm->advanceStage($tid, 'AWARDED', 'a', $uidCrm, $unmCrm);
$tm->saveContractTerms($tid, ['contract_value'=>30000000], $uidFm, $ugFm, $unmFm);
$rC = $ac->submit(['chain_key'=>'TENDER_CONTRACT_APPROVAL','entity_type'=>'TENDER_CONTRACT','entity_id'=>$tid,'ref_label'=>'x','return_url'=>PROOT.'Tenders/view/'.$tid,'user_id'=>$uidCrm,'user_name'=>$unmCrm,'user_group'=>$ugCrm]);
$aidC = (int)$rC['approval_id'];
$ac->approve(['approval_id'=>$aidC,'user_id'=>$uidFm,'user_name'=>$unmFm,'user_group'=>$ugFm,'comment'=>'','assignments'=>['ACCOUNTANT'=>['user_id'=>$accId,'user_name'=>$accNm],'PROCUREMENT'=>['user_id'=>$procId,'user_name'=>$procNm]]]);
$ac->approve(['approval_id'=>$aidC,'user_id'=>$uidExec,'user_name'=>$unmExec,'user_group'=>$ugExec,'comment'=>'','assignments'=>['PROJECT_MANAGER'=>['user_id'=>$pmId,'user_name'=>$pmNm]]]);
$aRow = $ac->approvalById($aidC);
$stg  = $ac->decodePayload((string)($aRow['step_payload_json'] ?? ''));
$tm->onContractApproved($tid, $uidExec, $unmExec, $stg['assignments'] ?? []);
$t = $tm->find($tid); $pid = (int)$t['linked_project_id'];
ok($pid > 0, "Project provisioned (id=$pid)");

/* ── 2 ── Empty submit blocked ─────────────────────────────────────── */
h('2. Draft + immediately submit → BLOCKED (no lines)');
$reqId = $sr->draft($pid, ['title' => 'Weekly float · site 03', 'purpose' => 'Cover fuel + meals for 2 techs', 'context_ref' => 'PM-visit-2026-08-19'], $pcId, $pcNm);
p("Requisition c0d=$reqId (SR#" . $sr->findById($reqId)['req_no'] . ")");
$res = $sr->submitForApproval($reqId, $pcId, $pcNm, 'Projects Coordinator');
ok(!$res['ok'] && (strpos($res['msg'], 'No lines') !== false || strpos($res['msg'], 'priced line') !== false),
   'Empty submit blocked: ' . $res['msg']);

/* ── 3 ── Add lines across categories ──────────────────────────────── */
h('3. Add lines across 4 categories');
$l1 = $sr->addLine($reqId, 'FUEL',       ['description'=>'Diesel · vehicle KAA1234', 'qty'=>60,  'unit'=>'L',  'unit_cost'=>3200]); // 192,000
$l2 = $sr->addLine($reqId, 'TRANSPORT',  ['description'=>'Motorbike hire (2 days)',  'qty'=>2,   'unit'=>'day','unit_cost'=>25000]); //  50,000
$l3 = $sr->addLine($reqId, 'MEALS',      ['description'=>'2 techs × 3 days',         'qty'=>6,   'unit'=>'day','unit_cost'=>15000]); //  90,000
$l4 = $sr->addLine($reqId, 'MISC',       ['description'=>'Small consumables',        'qty'=>1,   'unit'=>'lot','unit_cost'=>18000]); //  18,000
$after = $sr->findById($reqId);
$expected = 60*3200 + 2*25000 + 6*15000 + 1*18000;
p("Total after 4 lines: " . number_format((float)$after['total_amount']) . " (expected " . number_format($expected) . ")");
ok(abs((float)$after['total_amount'] - $expected) < 0.01, 'Total rolls up correctly (350,000)');
ok(count($sr->linesFor($reqId)) === 4, '4 lines on requisition');

/* ── 4 ── Delete line + recompute ──────────────────────────────────── */
h('4. Delete misc line → total recomputes');
$sr->deleteLine($l4);
$after = $sr->findById($reqId);
$expected2 = 60*3200 + 2*25000 + 6*15000;
ok(abs((float)$after['total_amount'] - $expected2) < 0.01, 'Total after delete = ' . number_format($expected2));
ok(count($sr->linesFor($reqId)) === 3, '3 lines remain (line count matches)');

/* ── 5 ── Guards ──────────────────────────────────────────────────── */
h('5. Guards: invalid category + non-positive qty → BLOCKED');
try { $sr->addLine($reqId, 'INVALID', ['description'=>'x','qty'=>1,'unit_cost'=>1]); ok(false, 'Invalid cat allowed'); }
catch (\Throwable $e) { ok(strpos($e->getMessage(), 'Invalid category') !== false, 'Invalid category blocked'); }
try { $sr->addLine($reqId, 'FUEL', ['description'=>'x','qty'=>0,'unit_cost'=>1000]); ok(false, 'Zero qty allowed'); }
catch (\Throwable $e) { ok(strpos($e->getMessage(), 'qty must be') !== false, 'Zero qty blocked'); }

/* ── 6 ── Submit ─────────────────────────────────────────────────── */
h('6. Submit into chain');
$res = $sr->submitForApproval($reqId, $pcId, $pcNm, 'Projects Coordinator');
if (!$res['ok']) boom('Submit failed: ' . $res['msg']);
$aid = (int)$res['approval_id'];
$req = $sr->findById($reqId);
ok($req['status'] === 'SUBMITTED' && (int)$req['approval_id'] === $aid, "Status=SUBMITTED · approval_id=$aid");
ok(!empty($req['submitted_on']), 'submitted_on stamped');

// Cannot edit lines while SUBMITTED
try { $sr->addLine($reqId, 'FUEL', ['description'=>'more diesel','qty'=>1,'unit_cost'=>3200]); ok(false, 'Line add allowed on SUBMITTED'); }
catch (\Throwable $e) { ok(strpos($e->getMessage(), 'DRAFT') !== false, 'Line add blocked on SUBMITTED'); }

/* ── 7 ── Walk 4-step chain ──────────────────────────────────────── */
h('7. Walk PC → PM → FM → Exec chain');
$ac->approve(['approval_id'=>$aid,'user_id'=>$pcId, 'user_name'=>$pcNm, 'user_group'=>'Projects Coordinator','comment'=>'OK']);
$ac->approve(['approval_id'=>$aid,'user_id'=>$pmId, 'user_name'=>$pmNm, 'user_group'=>'Project Manager',     'comment'=>'OK']);
$ac->approve(['approval_id'=>$aid,'user_id'=>$uidFm,'user_name'=>$unmFm,'user_group'=>$ugFm,               'comment'=>'OK']);
$ac->approve(['approval_id'=>$aid,'user_id'=>$uidExec,'user_name'=>$unmExec,'user_group'=>$ugExec,         'comment'=>'OK']);
$aRow = $ac->approvalById($aid);
$sr->onChainDecision($reqId, (string)$aRow['status']);
$req = $sr->findById($reqId);
p("Post-terminal: status={$req['status']} · approved_on={$req['approved_on']}");
ok($req['status'] === 'APPROVED' && !empty($req['approved_on']), 'Requisition APPROVED (awaits Accountant issue in later slice)');

/* ── 8 ── Cancel path ──────────────────────────────────────────────── */
h('8. Fresh draft → cancel (guard: cannot cancel APPROVED/SUBMITTED)');
$req2Id = $sr->draft($pid, ['title' => 'Secondary float'], $pcId, $pcNm);
$sr->cancel($req2Id, $pcId);
$req2 = $sr->findById($req2Id);
ok($req2['status'] === 'CANCELLED', 'Draft cancelled → CANCELLED');
try { $sr->cancel($reqId, $pcId); ok(false, 'Cancel allowed on APPROVED'); }
catch (\Throwable $e) { ok(strpos($e->getMessage(), 'Only DRAFT') !== false, 'Cancel on APPROVED blocked'); }

/* ── 9 ── linesByCategory ──────────────────────────────────────────── */
h('9. linesByCategory returns per-category totals');
$byCat = $sr->linesByCategory($reqId);
foreach (['FUEL','TRANSPORT','MEALS'] as $c) p("  · $c → " . count($byCat[$c]['lines']) . " line · total " . number_format($byCat[$c]['total']));
ok($byCat['FUEL']['total']      == 60*3200,    'FUEL total 192,000');
ok($byCat['TRANSPORT']['total'] == 2*25000,    'TRANSPORT total 50,000');
ok($byCat['MEALS']['total']     == 6*15000,    'MEALS total 90,000');
ok(count($byCat['MISC']['lines']) === 0,       'MISC has 0 lines (line deleted in step 4)');

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