38 lines
11 KiB
PHP
38 lines
11 KiB
PHP
<?php
|
|
if(!($s['integrated']??false))throw new LogicException('Integrated fixture required');
|
|
$hostBudgetBefore=hash('sha256',json_encode(DB::table('cb_budget_entries')->orderBy('id')->get()));
|
|
$ok($db->table('requisitions')->where('tenant_id',$n['tenant'])->count()===0&&$db->table('pa_obligations')->count()===0,'single composition begins before request and debt');
|
|
$r=$call('actor','/purchasing/workspace');$ok($r['status']===200&&str_contains($r['body'],'/purchasing/sourcing')&&str_contains($r['body'],'/purchasing/requests')&&!str_contains($r['body'],'href="/purchasing/obligations"'),'buyer navigation includes only existing authorised stages');
|
|
$r=$call('validator','/purchasing/workspace');$ok($r['status']===200&&str_contains($r['body'],'href="/purchasing/receipts"')&&!str_contains($r['body'],'href="/purchasing/invoices"'),'warehouse hub does not expose financial navigation');
|
|
$ok($call('requester','/api/purchasing-requests/'.$n['foreign_requisition'])['status']===403,'foreign source remains isolated in combined book');
|
|
$requestCommand=function($role,$id,$action,$data)use($call){$url='/api/purchasing-requests';$r=$call($role,$id?$url.'/'.$id:$url);$p=$call($role,$url.'/'.$id.'/preview',['operation_key'=>(string)Str::uuid(),'revision'=>$id?$r['data']['revision']:$r['data']['new_revision'],'action'=>$action,'data'=>$data]);if($p['status']!==200)throw new RuntimeException('Request preview '.$p['status']);return $call($role,$url.'/'.$id.'/confirm',['preview'=>$p['data']['preview']]);};
|
|
$r=$requestCommand('requester',0,'save',['cost_center_id'=>$n['centre'],'priority'=>'normal','needed_by'=>date('Y-m-d',strtotime('+30 days')),'justification'=>'Necessidade sintetica integrada sem copiar origens.','items'=>[['description'=>'Material integrado QA','category'=>'Material','purchase_type'=>'consumable','quantity'=>'1.000','unit'=>'UN','unit_price'=>'100.00','tax_rate'=>'0']]]);$request=$r['data']['request_id'];$ok($r['status']===201,'request created through native lifecycle in combined composition');
|
|
$r=$requestCommand('requester',$request,'submit',[]);$ok($r['status']===201&&$r['data']['status']==='pending','submission creates native reservation in same book');
|
|
$r=$requestCommand('approver',$request,'decide',['decision'=>'approved','comment'=>'Aprovacao distinta da necessidade sintetica.']);$ok($r['status']===201&&$r['data']['status']==='approved','distinct head approves new request');
|
|
$src='/api/purchasing-sourcing/requests/'.$request;$r=$call('actor',$src);$ok($r['status']===200&&$r['data']['capabilities']['quote']===true,'approved request flows to sourcing without binding rewrite');$line=(int)$db->table('requisition_items')->where('requisition_id',$request)->value('id');
|
|
$sourceCommand=function($role,$action,$data)use($call,$src){$r=$call($role,$src);$p=$call($role,$src.'/preview',['operation_key'=>(string)Str::uuid(),'revision'=>$r['data']['revision'],'action'=>$action,'data'=>$data]);if($p['status']!==200)throw new RuntimeException('Sourcing '.$action.' preview '.$p['status']);return $call($role,$src.'/confirm',['preview'=>$p['data']['preview']]);};
|
|
$quotes=[];foreach($n['supplier_ids']as$i=>$supplier){$r=$sourceCommand('actor','quote',['supplier_id'=>$supplier,'reference'=>'QA-INTEGRATED-Q'.($i+1),'delivery_days'=>5,'valid_until'=>date('Y-m-d',strtotime('+30 days')),'payment_terms'=>'Condicoes documentais QA sem vencimento presumido','no_conflict'=>true,'items'=>[['requisition_item_id'=>$line,'unit_price'=>(string)(100+$i*20),'discount_type'=>'none','discount_value'=>'0']]]);if($r['status']!==201)throw new RuntimeException('Quotation failed');$quotes[]=$r['data']['quotation_id'];}
|
|
$ok(count($quotes)===3&&$db->table('quotations')->where('requisition_id',$request)->count()===3,'three proposals retain new request and original supplier identities');
|
|
foreach($quotes as$quote)foreach(['actor','issuer']as$role){$r=$sourceCommand($role,'evaluate',['quotation_id'=>$quote,'quality'=>'8','history'=>'5','payment'=>'8','comment'=>'Avaliacao individual sintetica integrada.','no_conflict'=>true]);if($r['status']!==201)throw new RuntimeException('Evaluation failed');}
|
|
$r=$call('actor',$src);$ok($r['data']['comparison']['ready']===true&&$r['data']['comparison']['top_ids']===[$quotes[0]],'native comparison ready after distinct individual evaluations');
|
|
$r=$sourceCommand('actor','select',['quotation_id'=>$quotes[0],'justification'=>'Escolha individual pela comparacao documentada.','delivery_date'=>date('Y-m-d',strtotime('+7 days')),'delivery_location'=>'Armazem QA']);$award=$r['data']['award_id'];$ok($r['status']===201&&$r['data']['status']==='pending','selection produces pending award rather than implicit approval');
|
|
$r=$sourceCommand('approver','decide',['award_id'=>$award,'decision'=>'approved','comment'=>'Conferencia distinta da adjudicacao integrada.']);$ok($r['status']===201&&$r['data']['status']==='approved','head identity survives invoice and payable composition and approves award');
|
|
$url='/api/purchasing-orders/awards/'.$award;$r=$call('actor',$url);$p=$call('actor',$url.'/preview',['revision'=>$r['data']['revision']]);$r=$call('actor',$url.'/issue',['preview'=>$p['data']['preview']]);$order=$r['data']['order'];$ok($r['status']===201&&$order['requisition_id']===$request,'same award issues canonical order for original new request');
|
|
$receipt='/api/purchasing-receipts/orders/'.$order['id'];$r=$call('validator',$receipt);$p=$call('validator',$receipt.'/preview',['revision'=>$r['data']['revision'],'idempotency_key'=>(string)Str::uuid(),'delivery_note'=>'QA-INTEGRATED-RECEIPT','received_on'=>date('Y-m-d'),'warehouse_id'=>$n['warehouse'],'items'=>[['purchase_order_item_id'=>$order['items'][0]['id'],'received_quantity'=>'1.000','accepted_quantity'=>'1.000','quality_status'=>'compliant','stock_item_id'=>$n['stock'],'notes'=>null]]]);$r=$call('validator',$receipt.'/confirm',['preview'=>$p['data']['preview']]);$ok($r['status']===201&&count($r['data']['movements'])===1,'warehouse receives actual new order without copying financial authority');
|
|
$iv='/api/purchasing-invoices/orders/'.$order['id'];$r=$call('issuer',$iv);$ok($r['status']===200&&$r['data']['can_prepare']===true,'new request origin is visible to invoice finance without static id binding');
|
|
$invoiceCommand=function($role,$action,$data)use($call,$iv){$r=$call($role,$iv);$p=$call($role,$iv.'/preview',['operation_key'=>(string)Str::uuid(),'revision'=>$r['data']['revision'],'action'=>$action,'reason'=>'Conferencia documental sintetica no percurso integrado.','data'=>$data]);if($p['status']!==200)throw new RuntimeException('Invoice preview '.$p['status']);return $call($role,$iv.'/confirm',['preview'=>$p['data']['preview']]);};
|
|
$r=$invoiceCommand('issuer','register',['number'=>'QA-INTEGRATED-INV-100','issued_on'=>date('Y-m-d'),'subtotal'=>'100.00','tax'=>'0.00','total'=>'100.00','items'=>[['purchase_order_item_id'=>$order['items'][0]['id'],'quantity'=>'1.000']]]);$invoice=$r['data']['invoice_id'];$ok($r['status']===201&&$db->table('pa_obligations')->count()===0,'pending document does not recognise debt');
|
|
$r=$invoiceCommand('reviewer','validate',['invoice_id'=>$invoice]);$ok($r['status']===201&&$r['data']['status']==='validated'&&$db->table('pa_obligations')->count()===0,'distinct document matching consumes original budget without implicit recognition');
|
|
$url='/api/purchasing-obligations/'.$invoice;$r=$call('issuer',$url);$ok($r['status']===200&&$r['data']['documents'][0]['missing']===[],'new validated invoice reaches same native payable source');
|
|
$payable=function($role,$action,$proposal=0)use($call,$url){$r=$call($role,$url);$p=$call($role,$url.'/preview',['operation_key'=>(string)Str::uuid(),'revision'=>$r['data']['revision'],'action'=>$action,'proposal_id'=>$proposal,'reason'=>'Responsabilidade sintetica conferida individualmente.']);if($p['status']!==200)throw new RuntimeException('Payable preview '.$p['status']);return $call($role,$url.'/confirm',['preview'=>$p['data']['preview']]);};
|
|
$r=$payable('issuer','prepare');$proposal=$r['data']['proposal_id'];$ok($r['status']===201&&$r['data']['state']==='prepared','explicit preparation retains invoice snapshot');$r=$payable('reviewer','validate',$proposal);$ok($r['status']===201&&$r['data']['state']==='recognised','distinct person recognises same invoice responsibility');
|
|
$r=$call('issuer',$url);$position=$r['data']['obligations'][0];$ok($position['principal_minor']===10000&&$position['balance_minor']===10000&&$position['due_on']===null&&$position['source_conflict']===false,'single100MZN principal and balance with unknown maturity');
|
|
$ok($db->table('budget_entries')->where('requisition_id',$request)->where('kind','reserve')->count()===1&&$db->table('budget_entries')->where('requisition_id',$request)->where('kind','commit')->count()===1&&$db->table('budget_consumptions')->where('invoice_id',$invoice)->count()===1&&(int)$db->table('budget_consumptions')->sum('amount_minor')===10000,'one reservation commitment and100MZN consumption in canonical budget');
|
|
$ok($db->table('pa_obligations')->count()===1&&$db->table('pa_events')->where('kind','payable.recognised')->count()===1&&$db->table('stock_movements')->count()===1&&$db->table('external_payments')->count()===0,'complete path leaves one debt and physical effect without payments');
|
|
$ok(hash('sha256',json_encode(DB::table('cb_budget_entries')->orderBy('id')->get()))===$hostBudgetBefore,'host budget entries unchanged through complete native path');
|
|
$r=$call('actor','/api/purchasing-suppliers/'.$n['supplier_ids'][0]);$ok($r['status']===200&&$r['data']['geography_enabled']===true,'supplier registry and common geography mounted alongside complete financial path');
|
|
$r=$call('actor','/api/geography/entities/company/'.$f['company']);$ok($r['status']===200&&$r['data']['location']===null&&DB::table('geo_locations')->count()===0,'composition does not assign company or supplier locations automatically');
|
|
$enter('issuer',1,['module'=>'commercial']);$ok($call('issuer',$url,null,1)['status']===409&&$call('issuer','/purchasing/workspace',null,1)['status']===409,'module boundary remains enforced on hub and final responsibility');$enter('issuer',1,['foreign'=>1]);$ok($call('issuer',$url,null,1)['status']===403,'unmapped unit cannot inspect integrated responsibility');
|
|
$ok($call('validator',$url)['status']===403&&$call('requester',$iv)['status']===403,'stage roles do not gain economic rights through shared navigation');
|
|
file_put_contents($out,json_encode(['passed'=>true,'checks'=>$checks,'same_context_whole_purchasing_path'=>true,'source_ids'=>['request'=>$request,'award'=>$award,'order'=>$order['id'],'invoice'=>$invoice,'obligation'=>$position['id']],'quotes'=>3,'evaluations'=>6,'principal_minor'=>10000,'native_consumption_minor'=>10000,'host_budget_unchanged'=>true,'new_right_names'=>[],'automatic_geography_assignments'=>0,'payments_created'=>0,'ledger_posted'=>false,'visual_review'=>false,'deployment_capacity_validated'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." integrated purchasing checks passed.\n";
|