| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- <?php
- /** php tests/water_ledger_test.php:事务回滚验证,导出临时文件自动清理。 */
- require dirname(__DIR__).'/vendor/autoload.php';
- \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
- \support\App::loadAllConfig(['route']);
- date_default_timezone_set('Asia/Shanghai');
- use support\Db;
- use plugin\saiadmin\app\model\water\WaterModel;
- use plugin\saiadmin\app\service\WaterBusiness;
- use plugin\saiadmin\app\service\WaterLedger;
- use plugin\saiadmin\app\controller\water\WaterController;
- use plugin\saiadmin\exception\ApiException;
- $checks = 0;
- function check(bool $condition, string $message): void {
- global $checks; ++$checks;
- if (!$condition) throw new RuntimeException($message);
- }
- function rejects(callable $callback, string $message): void {
- try { $callback(); } catch (ApiException $e) { check(true,$message); return; }
- throw new RuntimeException($message);
- }
- function workbook(string $content): array {
- $path = tempnam(sys_get_temp_dir(),'ledger-test-');
- $reader = new \OpenSpout\Reader\XLSX\Reader();
- try {
- file_put_contents($path,$content);
- $reader->open($path);
- $rows = [];
- foreach ($reader->getSheetIterator() as $sheet) {
- foreach ($sheet->getRowIterator() as $row) $rows[] = $row->toArray();
- }
- return $rows;
- } finally { $reader->close(); unlink($path); }
- }
- $ledger = new WaterLedger();
- $business = new WaterBusiness();
- $tag = 'LEDGER-TEST-'.bin2hex(random_bytes(5));
- $before = \plugin\saiadmin\app\model\water\WaterLedger::query()->count();
- Db::beginTransaction();
- try {
- $enterprise = $business->save('enterprise',['name'=>$tag.'企业','contact'=>'测试','phone'=>'TEST','address'=>'测试地址'],1)['id'];
- $sku = $business->save('sku',['name'=>$tag.'商品','liters'=>18.9],1)['id'];
- $ids = [];
- for ($i=0; $i<25; $i++) {
- $ids[] = (int)\plugin\saiadmin\app\model\water\WaterLedger::query()->insertGetId([
- 'enterprise_id'=>$enterprise, 'sku_id'=>$sku, 'kind'=>$i%2 ? 'site_count' : 'inbound',
- 'quantity'=>$i===24 ? -3 : $i, 'reference'=>$tag.'-'.$i,
- 'note'=>$i===24 ? '=1+1' : '查说明-'.$i, 'created_by'=>1,
- 'created_at'=>'2026-09-'.($i<20 ? '09' : '10').' '.($i===24 ? '23:59:59' : '00:00:00'),
- ]);
- }
- $first = $business->listing('ledger',['keyword'=>' '.$tag.' ','page'=>1,'limit'=>10]);
- $second = $ledger->listing(['keyword'=>$tag,'page'=>2,'limit'=>10]);
- $last = $ledger->listing(['keyword'=>$tag,'page'=>3,'limit'=>10]);
- check($first['total']===25 && count($first['data'])===10 && count($second['data'])===10 && count($last['data'])===5,'10/10/5 pagination');
- check(array_column(array_merge($first['data'],$second['data'],$last['data']),'id')===array_reverse($ids),'stable descending IDs without duplicate or skipped rows');
- check(count($ledger->listing(['keyword'=>$tag,'page'=>4,'limit'=>10])['data'])===0,'out of range page');
- check($ledger->listing(['keyword'=>$tag.'企业'])['total']===25,'enterprise keyword');
- check($ledger->listing(['keyword'=>$tag.'商品'])['total']===25,'SKU keyword');
- check($ledger->listing(['keyword'=>$tag.'-24'])['total']===1,'reference keyword');
- check($ledger->listing(['keyword'=>'查说明-23'])['total']===1,'note keyword');
- check($ledger->listing(['keyword'=>$tag,'kind'=>'inbound'])['total']===13,'kind filter');
- $filter = ['keyword'=>$tag,'kind'=>'inbound','start_date'=>'2026-09-10','end_date'=>'2026-09-10'];
- $filtered = $ledger->listing($filter);
- check($filtered['total']===3 && $filtered['data'][0]['created_at']==='2026-09-10 23:59:59','combined filters include final second');
- check($ledger->listing(['keyword'=>$tag,'end_date'=>'2026-09-09'])['total']===20,'end date boundary');
- check(count($first['kinds'])===12 && $first['data'][0]['kind_label']==='仓库入库','localized kinds include fulfillment movements');
- 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) {
- rejects(fn()=>$ledger->listing($invalid),'invalid list filters rejected');
- rejects(fn()=>$ledger->export($invalid),'invalid export filters rejected');
- }
- $exported = workbook($ledger->export(['keyword'=>$tag,'page'=>2,'limit'=>1]));
- check(count($exported)===26,'export includes all 25 matching rows regardless of page');
- check(array_column(array_slice($exported,1),0)===array_reverse($ids),'export order equals list');
- check($exported[1][4]===-3 && $exported[1][6]==="'=1+1",'negative quantity stays numeric and formula-like note safe');
- $filteredExport = workbook($ledger->export($filter));
- check(count($filteredExport)===4 && array_column(array_slice($filteredExport,1),0)===array_column($filtered['data'],'id'),'filtered export equals list');
- check(count(workbook($ledger->export(['keyword'=>$tag.'-missing'])))===1,'empty export has headers only');
- $reflection = new ReflectionClass(WaterController::class);
- $controller = $reflection->newInstanceWithoutConstructor();
- $reflection->getProperty('adminId')->setValue($controller,0);
- $request = new \support\Request("GET /core/water/ledger HTTP/1.1\r\nHost: localhost\r\n\r\n");
- rejects(fn()=>$controller->ledger($request),'list rejects unauthorized user');
- rejects(fn()=>$controller->exportLedger($request),'export rejects unauthorized user');
- $reflection->getProperty('adminId')->setValue($controller,1);
- $body = json_encode(['keyword'=>$tag,'kind'=>'inbound']);
- $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);
- $response = $controller->exportLedger($request);
- check($response->getStatusCode()===200 && count(workbook($response->rawBody()))===14,'authorized JSON export controller');
- } finally { Db::rollback(); }
- check(\plugin\saiadmin\app\model\water\WaterLedger::query()->count()===$before,'fixtures rolled back');
- echo "PASS: $checks water ledger checks; all test records rolled back.\n";
|