<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');define('CDX_BILL_CONTEXT',true);require __DIR__.'/preflight.php';
$stage=$argv[1]??'original';$source=json_decode(file_get_contents(__DIR__.'/private-source.json'),true);$bills=$source['bills_in_scope'];
if(!$bills)throw new RuntimeException('No natural bills');
foreach(['controller'=>'ef2e8f2a835f21ad92d6dfff7b49e71a79a9cbd2d2dfefc204f452ea4a2e1f1d','logic'=>'42bf383374c80a8774c014048c189f5c0af135ed30ef71715c05b0c97719f8ca']as $kind=>$sha){$f=__DIR__.'/source/application/p/'.$kind.'/BillingRecord.php';if($stage!=='green'&&hash_file('sha256',$f)!==$sha)throw new RuntimeException('Source SHA drift');}
$money=[];foreach($source['money_text']as $table=>$rows)foreach($rows as $r)$money[$table][(int)$r['id']]=$r;
$number=static function($v):string{return number_format((float)$v,2,'.','');};
$time=static function($v):string{$s=substr((string)$v,0,19);return !$s||$s==='2000-01-01'||$s==='2000-01-01 00:00:00'?'--':$s;};
$bill=static function($r)use($money,$number,$time){$status=(int)$r['status'];$front=[10=>'draft',20=>'waiting',30=>'paid',40=>'expired'];$labels=[10=>'待核对',20=>'待缴费',30=>'已缴费',40=>'超时未缴费'];$v=['id'=>(int)$r['id'],'bill_id'=>(int)$r['id']];foreach(['bill_no'=>'billNo','bill_month'=>'month','student_name'=>'studentName','student_no'=>'studentNo','department_name'=>'department']as $k=>$a){$v[$k]=$r[$k];$v[$a]=$r[$k];}$v['department_uuid']=$r['department_uuid'];$v['parent_mobile']=$r['parent_mobile'];$v['parentPhone']=trim((string)$r['parent_mobile'])===''?'--':preg_replace('/^(\d{3})\d{4}(\d+)/','$1****$2',trim((string)$r['parent_mobile']));$m=$money['billing_record'][(int)$r['id']];$v['amount']=(float)$m['cdx_amount'];$v['original_amount']=$number($m['cdx_original_amount']);$v['status']=$front[$status]??'';$v['status_value']=$status;$v['status_label']=$labels[$status]??'';foreach(['issue_time'=>'startTime','expire_time'=>'validEnd','pay_time'=>'payTime']as $k=>$a){$v[$k]=$time($r[$k]);$v[$a]=$v[$k];}$v['pay_method']=$r['pay_method'];$v['trade_id']=(int)$r['trade_id'];return $v;};
$meal=static function($id)use($source,$money,$number){$rows=array_values(array_filter($source['billing_record_detail'],static function($r)use($id){return (int)$r['bill_id']===$id&&(int)$r['store_id']===10001&&(int)$r['is_delete']===0;}));usort($rows,static function($a,$b){return (int)$a['meal_type']<=>(int)$b['meal_type'];});$v=[];foreach($rows as $r){$type=(int)$r['meal_type'];$m=$money['billing_record_detail'][(int)$r['id']];$v[]=['meal_type'=>$type,'key'=>[1=>'breakfast',2=>'lunch',3=>'dinner'][$type]??'','name'=>[1=>'早餐',2=>'午餐',3=>'晚餐'][$type]??'','original_count'=>(int)$r['original_count'],'count'=>(int)$r['adjusted_count'],'original_amount'=>$number($m['cdx_original_amount']),'amount'=>$number($m['cdx_adjusted_amount']),'raw_pay_price'=>$number($m['cdx_raw_pay_price']??0),'raw_refund_amount'=>$number($m['cdx_raw_refund_amount']??0),'raw_amount'=>$number($m['cdx_raw_amount']??$m['cdx_original_amount'])];}return $v;};
$stats=static function($rows)use($money,$number){$s=['total_people'=>count($rows),'total_amount'=>'0.00','wait_check_people'=>0,'wait_check_amount'=>'0.00','paid_people'=>0,'paid_amount'=>'0.00','wait_people'=>0,'wait_amount'=>'0.00'];$cents=['total'=>0,'wait_check'=>0,'paid'=>0,'wait'=>0];foreach($rows as $r){$n=(int)round((float)$money['billing_record'][(int)$r['id']]['cdx_amount']*100);$cents['total']+=$n;$key=[10=>'wait_check',20=>'wait',30=>'paid',40=>'wait'][(int)$r['status']]??null;if($key){$s[$key.'_people']++;$cents[$key]+=$n;}}foreach($cents as $key=>$n)$s[$key.'_amount']=$number($n/100);$s['payable_people']=$s['total_people'];$s['payable_amount']=$s['total_amount'];$s['paid_people_rate']=count($rows)?round($s['paid_people']/count($rows)*100,1):0;$s['paid_amount_rate']=$cents['total']>0?round(floor($cents['paid']/$cents['total']*10000)/100,1):0;$s['paid_rate']=$s['paid_amount_rate'];return $s;};
$exports=static function($rows)use($bill,$meal){$v=[];foreach($rows as $r){$row=$bill($r);$by=array_column($meal((int)$r['id']),null,'meal_type');foreach([1=>'breakfast',2=>'lunch',3=>'dinner']as $n=>$key){$row[$key.'_count']=$by[$n]['count']??0;$row[$key.'_amount']=$by[$n]['amount']??'0.00';}$v[]=$row;}return $v;};
$spec=[];$add=static function($label,$method,$action,$params,$expected,$code=0)use(&$spec){$spec[]=['label'=>$label,'method'=>$method,'path'=>'/p/billingrecord/'.$action,'params'=>$params,'expected'=>$expected,'code'=>$code];};
$first=$bills[0];$id=(int)$first['id'];$list=['total'=>count($bills),'per_page'=>15,'current_page'=>1,'last_page'=>(int)ceil(count($bills)/15),'data'=>array_map($bill,array_slice($bills,0,15))];
$add('get-list','GET','list',[],$list);$add('get-lists','GET','lists',[],$list);$add('get-statistics','GET','statistics',[],$stats($bills));$add('get-detail-query','GET','detail',['bill_id'=>$id],null,1);$add('get-logs-query','GET','logs',['bill_id'=>$id],null,1);$add('get-export','GET','export',[],$exports($bills));
// Read one natural bill per status and ensure the real sentinel branch is covered.
$selected=[];foreach($bills as $r){$k=(string)$r['status'];if(!isset($selected[$k]))$selected[$k]=$r;}
foreach($selected as $status=>$r){$d=$bill($r);$d['meals']=$meal((int)$r['id']);$add('post-detail-status-'.$status,'POST','detail',['bill_id'=>(int)$r['id']],$d);}
$logs=array_values(array_filter($source['billing_record_log'],static function($r)use($id){return (int)$r['bill_id']===$id&&(int)$r['store_id']===10001;}));usort($logs,static function($a,$b){return (int)$a['id']<=>(int)$b['id'];});
// Logs retain every source field and driver type; API time follows legacy second precision.
foreach($logs as &$log)if(is_string($log['create_time']??null)&&preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{1,6}$/',$log['create_time']))$log['create_time']=substr($log['create_time'],0,19);unset($log);
$add('post-logs-natural','POST','logs',['bill_id'=>$id],$logs);
$month=$first['bill_month'];$monthRows=array_values(array_filter($bills,static function($r)use($month){return $r['bill_month']===$month;}));$add('post-export-month','POST','export',['bill_month'=>$month],$exports($monthRows));$add('post-statistics-month','POST','statistics',['bill_month'=>$month],$stats($monthRows));
$add('post-export-empty','POST','export',['bill_month'=>'1900-01'],[]);$add('post-statistics-empty','POST','statistics',['bill_month'=>'1900-01'],$stats([]));$add('post-detail-missing','POST','detail',[],null,1);$add('post-logs-missing','POST','logs',[],null,1);
if($stage==='logs-red')$spec=array_values(array_filter($spec,static function($s){return $s['label']==='post-logs-natural';}));
require_once $root.'/application/base/Security.php';$admin=$source['admin'];$token=\app\base\Security::encrypt(implode("\t",[(string)$admin['id'],(string)$admin['uuid'],'CDXBILLADMIN','0','','','CDXBILLADMIN']));
$items=[];foreach($spec as $i=>$s){$url='http://127.0.0.1:'.($lane==='B'?18301:18302).$s['path'];if($s['method']==='GET'&&$s['params'])$url.='?'.http_build_query($s['params']);$request=['method'=>$s['method'],'header'=>[['key'=>'token','value'=>$token],['key'=>'Content-Type','value'=>'application/x-www-form-urlencoded']],'url'=>$url];if($s['method']==='POST')$request['body']=['mode'=>'urlencoded','urlencoded'=>array_map(static function($k,$v){return ['key'=>$k,'value'=>(string)$v,'type'=>'text'];},array_keys($s['params']),array_values($s['params']))];$code=$s['code']===1?'2':0;$tests=["pm.test('HTTP 200',()=>pm.response.to.have.status(200));","const body=pm.response.json();","pm.test('business code and wire type',()=>pm.expect(body.code).to.eql(".json_encode($code)."));"];if($s['expected']!==null){$tests[]='const expected='.json_encode($s['expected'],JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION).';';$tests[]="pm.test('independent original-table all values and JSON shape',()=>pm.expect(body.data).to.deep.equal(expected));";}else{$tests[]="pm.test('common missing bill error',()=>pm.expect(body.message).to.eql('账单不存在'));";}$items[]=['id'=>'billing-'.$i,'name'=>$s['label'],'type'=>'http','metaInfo'=>['type'=>'HTTP'],'request'=>$request,'event'=>[['listen'=>'test','script'=>['type'=>'text/javascript','exec'=>$tests]]]];}
foreach(['private-collection.apifox-cli.json'=>['apifoxCli'=>'2.2.8','info'=>['name'=>'Billing read-only '.$lane.' '.$stage],'item'=>[['name'=>'Frozen billing reads','item'=>$items]]],'private-spec.json'=>$spec]as $f=>$v){file_put_contents(__DIR__.'/'.$f,json_encode($v,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));chmod(__DIR__.'/'.$f,0600);}
echo json_encode(['lane'=>$lane,'stage'=>$stage,'prepared_http_cases'=>count($items),'source_bills'=>count($bills),'source_status_counts'=>$report['in_scope_status_counts'],'expected_nonempty_logs'=>count($logs),'original_table_oracle'=>true]),"\n";
