table('store_receipt_product_cost_rows')->insert([ $this->row('pending-both', 'HX-A', null, null, 2, null), $this->row('pending-price', 'HX-B', '崔桥工厂仓', null, 3, null), $this->row('pending-warehouse', 'HX-C', '', 10, 1, 10), $this->row('valid', 'HX-D', '崔桥工厂仓', 10, 1, 10), $this->row('df-normal', 'HX-E', null, 10, 1, 10, productCode: 'DF-ACCESSORY-1'), $this->row('df-price', 'HX-F', null, null, 1, null, productCode: 'df-ACCESSORY-2'), $this->row('other-store', 'OTHER', null, null, 1, null, '其他店铺'), $this->row('other-month', 'OLD', null, null, 1, null, '京东-HX旗舰店104', '2026-08-01'), ]); $response = $this->loginAs()->getJson(route( 'business.financial-operations.product-costs.pending-details', [ 'month' => '2026-09', 'store_name' => 'HX', ], )); $response ->assertOk() ->assertJsonPath('summary.pending_rows', 4) ->assertJsonPath('summary.pending_cost_rows', 3) ->assertJsonPath('summary.pending_cost_quantity', 6) ->assertJsonPath('summary.missing_warehouse_rows', 2) ->assertJsonPath('pagination.total', 4) ->assertJsonPath('pagination.page', 1); $cacheControl = (string) $response->headers->get('Cache-Control'); $this->assertStringContainsString('private', $cacheControl); $this->assertStringContainsString('no-store', $cacheControl); $rows = collect($response->json('rows'))->keyBy('online_order_no'); $this->assertCount(4, $rows); $this->assertSame(['商品单价缺失', '仓库简称未匹配'], $rows['HX-A']['reasons']); $this->assertTrue($rows['HX-A']['warehouse_missing']); $this->assertSame(['商品单价缺失'], $rows['HX-B']['reasons']); $this->assertSame(['仓库简称未匹配'], $rows['HX-C']['reasons']); $this->assertTrue($rows['HX-C']['warehouse_missing']); $this->assertSame(['商品单价缺失'], $rows['HX-F']['reasons']); $this->assertFalse($rows['HX-F']['warehouse_missing']); $this->assertFalse($rows->has('HX-D')); $this->assertFalse($rows->has('HX-E')); } public function test_repaired_row_disappears_when_modal_data_is_reloaded(): void { $mirror = DB::connection('business_mirror'); $mirror->table('store_receipt_product_cost_rows')->insert( $this->row('repair-me', 'REPAIR-1', null, null, 1, null) ); $this->loginAs(); $url = route('business.financial-operations.product-costs.pending-details', [ 'month' => '2026-09', ]); $this->getJson($url) ->assertOk() ->assertJsonPath('pagination.total', 1) ->assertJsonPath('rows.0.online_order_no', 'REPAIR-1'); $mirror->table('store_receipt_product_cost_rows') ->where('row_key', 'repair-me') ->update([ 'warehouse_short_name' => '崔桥工厂仓', 'unit_price' => 10, 'product_cost' => 10, ]); $this->getJson($url) ->assertOk() ->assertJsonPath('summary.pending_rows', 0) ->assertJsonPath('summary.pending_cost_rows', 0) ->assertJsonPath('summary.missing_warehouse_rows', 0) ->assertJsonPath('pagination.total', 0) ->assertJsonCount(0, 'rows'); } public function test_pending_type_filter_and_csv_export_use_modal_filters(): void { DB::connection('business_mirror')->table('store_receipt_product_cost_rows')->insert([ $this->row('both', 'FILTER-A', null, null, 2, null), $this->row('price', 'FILTER-B', '崔桥工厂仓', null, 3, null), $this->row('warehouse', 'FILTER-C', '', 10, 1, 10), $this->row('valid', 'FILTER-D', '崔桥工厂仓', 10, 1, 10), ]); $this->loginAs(); $this->getJson(route('business.financial-operations.product-costs.pending-details', [ 'month' => '2026-09', 'store_name' => 'HX', 'pending_type' => 'missing_warehouse', ])) ->assertOk() ->assertJsonPath('pagination.total', 2) ->assertJsonPath('rows.0.online_order_no', 'FILTER-A') ->assertJsonPath('rows.1.online_order_no', 'FILTER-C'); $this->getJson(route('business.financial-operations.product-costs.pending-details', [ 'month' => '2026-09', 'store_name' => 'HX', 'pending_type' => 'both', ])) ->assertOk() ->assertJsonPath('pagination.total', 1) ->assertJsonPath('rows.0.online_order_no', 'FILTER-A'); $response = $this->get(route('business.financial-operations.product-costs.pending-details.export', [ 'month' => '2026-09', 'store_name' => 'HX', 'pending_type' => 'missing_price', ])); $response ->assertOk() ->assertDownload('product-cost-pending-details_2026-09.csv'); $csv = $response->streamedContent(); $this->assertStringStartsWith("\xEF\xBB\xBF", $csv); $this->assertStringContainsString('线上单号', $csv); $this->assertStringContainsString('FILTER-A', $csv); $this->assertStringContainsString('FILTER-B', $csv); $this->assertStringNotContainsString('FILTER-C', $csv); $this->assertStringNotContainsString('FILTER-D', $csv); } private function row( string $rowKey, string $onlineOrderNo, ?string $warehouseShortName, int|float|null $unitPrice, int|float $quantity, int|float|null $productCost, string $storeName = '京东-HX旗舰店104', string $reportMonth = '2026-09-01', ?string $productCode = null ): array { return [ 'report_month' => $reportMonth, 'row_key' => $rowKey, 'platform' => '京东账单', 'occurred_date' => $reportMonth, 'store_name' => $storeName, 'online_order_no' => $onlineOrderNo, 'warehouse' => '简尚-崔桥工厂仓', 'warehouse_short_name' => $warehouseShortName, 'product_code' => $productCode ?? 'SKU-' . $rowKey, 'product_quantity' => $quantity, 'unit_price' => $unitPrice, 'product_cost' => $productCost, 'remark' => null, 'imported_at' => now(), ]; } }