- 新增共享镜像表 business_internal_flow_records 与整份替换导入命令 internal-flow:import - 按 Excel 表头展示抬头、标签、预计9月销量、开票、所需进项成本、收票、剩余缺口与开票比例 - 复用公司基础信息的字段查询、表头排序、筛选字段与导出 Excel 交互 - 新增导入与页面测试,覆盖合计行跳过、重导入替换、筛选排序与未导入空状态
186 lines
7.6 KiB
PHP
186 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Console;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Modules\EcommerceFinance\Repositories\InternalFlowReportRepository;
|
|
use Modules\EcommerceFinance\Services\InternalFlowWorkbookImporter;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
use Tests\TestCase;
|
|
|
|
class InternalFlowReportImportTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite',
|
|
'database' => ':memory:',
|
|
'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
]);
|
|
DB::purge('business_mirror');
|
|
|
|
$migration = require base_path(
|
|
'database/migrations/2026_10_05_000000_create_business_internal_flow_records_table.php'
|
|
);
|
|
$migration->up();
|
|
}
|
|
|
|
public function testImportKeepsWorkbookValuesAndSkipsTotalRows(): void
|
|
{
|
|
$path = $this->writeWorkbook($this->dataRows());
|
|
|
|
try {
|
|
$result = app(InternalFlowWorkbookImporter::class)->import($path);
|
|
} finally {
|
|
@unlink($path);
|
|
}
|
|
|
|
$this->assertSame(3, $result['imported']);
|
|
$this->assertSame(1, $result['skipped']);
|
|
|
|
$rows = DB::connection('business_mirror')
|
|
->table(InternalFlowWorkbookImporter::TABLE)
|
|
->orderBy('sort_order')
|
|
->get();
|
|
|
|
$this->assertCount(3, $rows);
|
|
$this->assertSame('示例甲供应链有限公司', $rows[0]->company_name);
|
|
$this->assertSame('1', (string) $rows[0]->tag);
|
|
$this->assertSame('1', $rows[0]->invoicing_ratio);
|
|
$this->assertEqualsWithDelta(1200000.0, (float) $rows[0]->projected_sales, 0.000001);
|
|
$this->assertSame(1, (int) $rows[0]->sort_order);
|
|
|
|
$this->assertSame('示例乙电子商务有限公司', $rows[1]->company_name);
|
|
$this->assertEqualsWithDelta(512345.6789, (float) $rows[1]->internal_invoiced, 0.000001);
|
|
$this->assertEqualsWithDelta(1833433.2211, (float) $rows[1]->required_input_cost, 0.000001);
|
|
$this->assertEqualsWithDelta(300000.5, (float) $rows[1]->internal_transfer_1, 0.000001);
|
|
$this->assertEqualsWithDelta(1200000.5, (float) $rows[1]->receipt_total, 0.000001);
|
|
$this->assertSame('0.42', $rows[1]->invoicing_ratio);
|
|
|
|
$this->assertSame('示例丙家居有限公司', $rows[2]->company_name);
|
|
$this->assertNull($rows[2]->invoiced_to_buyer);
|
|
$this->assertEqualsWithDelta(220000.11, (float) $rows[2]->receipt_total, 0.000001);
|
|
$this->assertSame('/', $rows[2]->invoicing_ratio);
|
|
$this->assertSame(basename($path), $rows[0]->source_file);
|
|
|
|
$this->assertSame(
|
|
0,
|
|
DB::connection('business_mirror')
|
|
->table(InternalFlowWorkbookImporter::TABLE)
|
|
->where('company_name', '合计')
|
|
->count(),
|
|
);
|
|
}
|
|
|
|
public function testImportReplacesPreviousDatasetAndCommandReportsCounts(): void
|
|
{
|
|
$first = $this->writeWorkbook($this->dataRows());
|
|
app(InternalFlowWorkbookImporter::class)->import($first);
|
|
@unlink($first);
|
|
|
|
$second = $this->writeWorkbook([
|
|
['示例乙电子商务有限公司', 2, 2345678.9, 456789.12, 512345.6789, 1833433.2211, 900000, null, null, 300000.5, 0, 0, 1200000.5, 633432.7211, 0.42],
|
|
]);
|
|
|
|
try {
|
|
$this->artisan('internal-flow:import', ['path' => $second])
|
|
->expectsOutputToContain('明细 1 行')
|
|
->assertSuccessful();
|
|
} finally {
|
|
@unlink($second);
|
|
}
|
|
|
|
$rows = DB::connection('business_mirror')
|
|
->table(InternalFlowWorkbookImporter::TABLE)
|
|
->get();
|
|
|
|
$this->assertCount(1, $rows);
|
|
$this->assertSame('示例乙电子商务有限公司', $rows[0]->company_name);
|
|
}
|
|
|
|
public function testRepositoryFiltersSortsAndPaginates(): void
|
|
{
|
|
$path = $this->writeWorkbook($this->dataRows());
|
|
app(InternalFlowWorkbookImporter::class)->import($path);
|
|
@unlink($path);
|
|
|
|
$repository = app(InternalFlowReportRepository::class);
|
|
|
|
$this->assertTrue($repository->ready());
|
|
|
|
$all = $repository->paginate(1, 50);
|
|
$this->assertSame(3, $all['total']);
|
|
$this->assertSame('示例甲供应链有限公司', $all['data'][0]['company_name']);
|
|
|
|
$filtered = $repository->paginate(1, 50, ['company_name' => '示例乙']);
|
|
$this->assertSame(1, $filtered['total']);
|
|
$this->assertSame('示例乙电子商务有限公司', $filtered['data'][0]['company_name']);
|
|
|
|
$tagged = $repository->paginate(1, 50, ['tag' => '4']);
|
|
$this->assertSame(1, $tagged['total']);
|
|
$this->assertSame('示例丙家居有限公司', $tagged['data'][0]['company_name']);
|
|
|
|
$sorted = $repository->paginate(1, 50, [], 'projected_sales', 'desc');
|
|
$this->assertSame('示例乙电子商务有限公司', $sorted['data'][0]['company_name']);
|
|
|
|
$unknownSort = $repository->paginate(1, 50, [], 'company_name; drop table users', 'desc');
|
|
$this->assertSame(3, $unknownSort['total']);
|
|
$this->assertSame('示例甲供应链有限公司', $unknownSort['data'][0]['company_name']);
|
|
|
|
$paged = $repository->paginate(2, 2);
|
|
$this->assertSame(3, $paged['total']);
|
|
$this->assertCount(1, $paged['data']);
|
|
$this->assertSame('示例丙家居有限公司', $paged['data'][0]['company_name']);
|
|
}
|
|
|
|
/**
|
|
* @return list<list<mixed>>
|
|
*/
|
|
private function dataRows(): array
|
|
{
|
|
return [
|
|
['示例甲供应链有限公司', 1, 1200000, 0, 1200000, 0, null, null, null, 0, 0, 0, 0, 0, 1],
|
|
['示例乙电子商务有限公司', 2, 2345678.9, 456789.12, 512345.6789, 1833433.2211, 900000, null, null, 300000.5, 0, 0, 1200000.5, 633432.7211, 0.42],
|
|
['示例丙家居有限公司', 4, 123456.78, null, null, 987654.32, null, null, null, 0, 0, 220000.11, 220000.11, 767654.21, '/'],
|
|
['合计', null, 3679135.68, 456789.12, 1712345.6789, 2821087.5411, 900000, 0, null, 300000.5, 0, 0, 1420000.61, 1401086.9311, '/'],
|
|
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<list<mixed>> $dataRows
|
|
*/
|
|
private function writeWorkbook(array $dataRows): string
|
|
{
|
|
$temporary = tempnam(sys_get_temp_dir(), 'internal-flow-');
|
|
$path = dirname($temporary) . DIRECTORY_SEPARATOR . 'internal-flow-' . basename($temporary) . '.xlsx';
|
|
rename($temporary, $path);
|
|
|
|
$spreadsheet = new Spreadsheet();
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
$sheet->setTitle(InternalFlowWorkbookImporter::SHEET_TITLE);
|
|
|
|
$sheet->fromArray([
|
|
['抬头', '标签', '预计9月销量', '开票', null, '所需进项成本', '收票', null, null, null, null, null, null, '剩余缺口', '开票比例'],
|
|
[null, null, null, null, null, null, '供应商', '索菲亚', '顾家', '内部流转(来源)', null, null, '合计', null, null],
|
|
[null, null, null, '给买家开票', '内部开票', null, null, null, null, '1', '2', '3', null, null, null],
|
|
...$dataRows,
|
|
], null, 'A1');
|
|
|
|
foreach (['A1:A3', 'B1:B3', 'C1:C3', 'D1:E2', 'F1:F3', 'G1:M1', 'H2:H3', 'I2:I3', 'J2:L2', 'M2:M3', 'N1:N3', 'O1:O3'] as $range) {
|
|
$sheet->mergeCells($range);
|
|
}
|
|
|
|
(new Xlsx($spreadsheet))->save($path);
|
|
$spreadsheet->disconnectWorksheets();
|
|
|
|
return $path;
|
|
}
|
|
}
|