water_ledger_test.php 6.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. <?php
  2. /** php tests/water_ledger_test.php:事务回滚验证,导出临时文件自动清理。 */
  3. require dirname(__DIR__).'/vendor/autoload.php';
  4. \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
  5. \support\App::loadAllConfig(['route']);
  6. date_default_timezone_set('Asia/Shanghai');
  7. use support\Db;
  8. use plugin\saiadmin\app\model\water\WaterModel;
  9. use plugin\saiadmin\app\service\WaterBusiness;
  10. use plugin\saiadmin\app\service\WaterLedger;
  11. use plugin\saiadmin\app\controller\water\WaterController;
  12. use plugin\saiadmin\exception\ApiException;
  13. $checks = 0;
  14. function check(bool $condition, string $message): void {
  15. global $checks; ++$checks;
  16. if (!$condition) throw new RuntimeException($message);
  17. }
  18. function rejects(callable $callback, string $message): void {
  19. try { $callback(); } catch (ApiException $e) { check(true,$message); return; }
  20. throw new RuntimeException($message);
  21. }
  22. function workbook(string $content): array {
  23. $path = tempnam(sys_get_temp_dir(),'ledger-test-');
  24. $reader = new \OpenSpout\Reader\XLSX\Reader();
  25. try {
  26. file_put_contents($path,$content);
  27. $reader->open($path);
  28. $rows = [];
  29. foreach ($reader->getSheetIterator() as $sheet) {
  30. foreach ($sheet->getRowIterator() as $row) $rows[] = $row->toArray();
  31. }
  32. return $rows;
  33. } finally { $reader->close(); unlink($path); }
  34. }
  35. $ledger = new WaterLedger();
  36. $business = new WaterBusiness();
  37. $tag = 'LEDGER-TEST-'.bin2hex(random_bytes(5));
  38. $before = \plugin\saiadmin\app\model\water\WaterLedger::query()->count();
  39. Db::beginTransaction();
  40. try {
  41. $enterprise = $business->save('enterprise',['name'=>$tag.'企业','contact'=>'测试','phone'=>'TEST','address'=>'测试地址'],1)['id'];
  42. $sku = $business->save('sku',['name'=>$tag.'商品','liters'=>18.9],1)['id'];
  43. $ids = [];
  44. for ($i=0; $i<25; $i++) {
  45. $ids[] = (int)\plugin\saiadmin\app\model\water\WaterLedger::query()->insertGetId([
  46. 'enterprise_id'=>$enterprise, 'sku_id'=>$sku, 'kind'=>$i%2 ? 'site_count' : 'inbound',
  47. 'quantity'=>$i===24 ? -3 : $i, 'reference'=>$tag.'-'.$i,
  48. 'note'=>$i===24 ? '=1+1' : '查说明-'.$i, 'created_by'=>1,
  49. 'created_at'=>'2026-09-'.($i<20 ? '09' : '10').' '.($i===24 ? '23:59:59' : '00:00:00'),
  50. ]);
  51. }
  52. $first = $business->listing('ledger',['keyword'=>' '.$tag.' ','page'=>1,'limit'=>10]);
  53. $second = $ledger->listing(['keyword'=>$tag,'page'=>2,'limit'=>10]);
  54. $last = $ledger->listing(['keyword'=>$tag,'page'=>3,'limit'=>10]);
  55. check($first['total']===25 && count($first['data'])===10 && count($second['data'])===10 && count($last['data'])===5,'10/10/5 pagination');
  56. check(array_column(array_merge($first['data'],$second['data'],$last['data']),'id')===array_reverse($ids),'stable descending IDs without duplicate or skipped rows');
  57. check(count($ledger->listing(['keyword'=>$tag,'page'=>4,'limit'=>10])['data'])===0,'out of range page');
  58. check($ledger->listing(['keyword'=>$tag.'企业'])['total']===25,'enterprise keyword');
  59. check($ledger->listing(['keyword'=>$tag.'商品'])['total']===25,'SKU keyword');
  60. check($ledger->listing(['keyword'=>$tag.'-24'])['total']===1,'reference keyword');
  61. check($ledger->listing(['keyword'=>'查说明-23'])['total']===1,'note keyword');
  62. check($ledger->listing(['keyword'=>$tag,'kind'=>'inbound'])['total']===13,'kind filter');
  63. $filter = ['keyword'=>$tag,'kind'=>'inbound','start_date'=>'2026-09-10','end_date'=>'2026-09-10'];
  64. $filtered = $ledger->listing($filter);
  65. check($filtered['total']===3 && $filtered['data'][0]['created_at']==='2026-09-10 23:59:59','combined filters include final second');
  66. check($ledger->listing(['keyword'=>$tag,'end_date'=>'2026-09-09'])['total']===20,'end date boundary');
  67. check(count($first['kinds'])===12 && $first['data'][0]['kind_label']==='仓库入库','localized kinds include fulfillment movements');
  68. foreach ([['kind'=>'purchase'],['start_date'=>'2026-02-30'],['end_date'=>'invalid'],['start_date'=>'2026-09-10','end_date'=>'2026-09-09'],['keyword'=>str_repeat('字',121)]] as $invalid) {
  69. rejects(fn()=>$ledger->listing($invalid),'invalid list filters rejected');
  70. rejects(fn()=>$ledger->export($invalid),'invalid export filters rejected');
  71. }
  72. $exported = workbook($ledger->export(['keyword'=>$tag,'page'=>2,'limit'=>1]));
  73. check(count($exported)===26,'export includes all 25 matching rows regardless of page');
  74. check(array_column(array_slice($exported,1),0)===array_reverse($ids),'export order equals list');
  75. check($exported[1][4]===-3 && $exported[1][6]==="'=1+1",'negative quantity stays numeric and formula-like note safe');
  76. $filteredExport = workbook($ledger->export($filter));
  77. check(count($filteredExport)===4 && array_column(array_slice($filteredExport,1),0)===array_column($filtered['data'],'id'),'filtered export equals list');
  78. check(count(workbook($ledger->export(['keyword'=>$tag.'-missing'])))===1,'empty export has headers only');
  79. $reflection = new ReflectionClass(WaterController::class);
  80. $controller = $reflection->newInstanceWithoutConstructor();
  81. $reflection->getProperty('adminId')->setValue($controller,0);
  82. $request = new \support\Request("GET /core/water/ledger HTTP/1.1\r\nHost: localhost\r\n\r\n");
  83. rejects(fn()=>$controller->ledger($request),'list rejects unauthorized user');
  84. rejects(fn()=>$controller->exportLedger($request),'export rejects unauthorized user');
  85. $reflection->getProperty('adminId')->setValue($controller,1);
  86. $body = json_encode(['keyword'=>$tag,'kind'=>'inbound']);
  87. $request = new \support\Request("POST /core/water/ledger/export HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nContent-Length: ".strlen($body)."\r\n\r\n".$body);
  88. $response = $controller->exportLedger($request);
  89. check($response->getStatusCode()===200 && count(workbook($response->rawBody()))===14,'authorized JSON export controller');
  90. } finally { Db::rollback(); }
  91. check(\plugin\saiadmin\app\model\water\WaterLedger::query()->count()===$before,'fixtures rolled back');
  92. echo "PASS: $checks water ledger checks; all test records rolled back.\n";