CaiWuXiTong/tests/Feature/Business/ProductPriceActionsTest.php

192 lines
7.9 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use Illuminate\Http\UploadedFile;
use Illuminate\Validation\ValidationException;
use Modules\EcommerceFinance\Repositories\ProductPriceMirrorRepository;
use Modules\EcommerceFinance\Services\ProductPriceWorkbookImporter;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Tests\Feature\FeatureTestCase;
class ProductPriceActionsTest extends FeatureTestCase
{
protected function grantAdminPermission(): void
{
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function test_export_downloads_all_rows_for_current_filters(): void
{
$this->grantAdminPermission();
$repository = \Mockery::mock(ProductPriceMirrorRepository::class);
$repository->shouldReceive('ready')->once()->andReturnTrue();
$repository->shouldReceive('initialized')->once()->andReturnTrue();
$repository->shouldReceive('exportRows')
->once()
->withArgs(function (array $filters, int $companyId): bool {
return $companyId === (int) $this->company->id
&& $filters === [
'barcode' => 'SKU-001',
'completed_from' => '2026-08-01',
'completed_to' => '2026-08-31',
];
})
->andReturn([(object) [
'upstream_key' => hash('sha256', 'export-row'),
'business_id' => 'B-001',
'barcode' => 'SKU-001',
'spu' => 'SPU-001',
'product_name' => '导出商品',
'category' => '家具',
'production_status' => '销售',
'minimum_price' => 120,
'price_limit' => 118,
'purchase_price' => 80,
'shipping_fee' => 10,
'jingxi_limit_price' => 100,
'approval_status' => '已完成',
'approval_result' => '已同意',
'workflow_title' => '价格审批',
'completed_at' => '2026-08-15 10:00:00',
'is_overridden' => 1,
'override_updated_at' => '2026-09-16 09:00:00',
]]);
$this->app->instance(ProductPriceMirrorRepository::class, $repository);
$response = $this->loginAs()->get(route('business.supplier-payables.product-prices.export', [
'barcode' => 'SKU-001',
'completed_from' => '2026-08-01',
'completed_to' => '2026-08-31',
]));
$response->assertOk()->assertDownload();
$content = $response->streamedContent();
$this->assertStringContainsString('记录标识', $content);
$this->assertStringContainsString('SKU-001', $content);
$this->assertStringContainsString('导出商品', $content);
$this->assertStringContainsString('人工维护', $content);
}
public function test_update_saves_manual_override_and_returns_to_current_filter(): void
{
$this->grantAdminPermission();
$key = hash('sha256', 'update-row');
$repository = \Mockery::mock(ProductPriceMirrorRepository::class);
$repository->shouldReceive('applyOverrides')
->once()
->withArgs(function (array $records, int $companyId, ?int $userId, string $source) use ($key): bool {
return $companyId === (int) $this->company->id
&& $userId === (int) $this->user->id
&& $source === 'manual'
&& $records[$key]['barcode'] === 'SKU-EDIT'
&& (float) $records[$key]['purchase_price'] === 82.5;
})
->andReturn(['created' => 1, 'updated' => 0]);
$this->app->instance(ProductPriceMirrorRepository::class, $repository);
$response = $this->loginAs()->patch(
route('business.supplier-payables.product-prices.update', ['upstreamKey' => $key])
. '?barcode=SKU&page=2',
[
'barcode' => 'SKU-EDIT',
'spu' => 'SPU-EDIT',
'product_name' => '编辑商品',
'purchase_price' => '82.50',
],
);
$response
->assertRedirect(route('business.pages.show', [
'section' => 'supplier-payables',
'page' => 'product-prices',
]) . '?barcode=SKU&page=2')
->assertSessionHas('success', '商品价格已保存。');
}
public function test_update_rejects_prices_with_more_than_four_decimal_places(): void
{
$this->grantAdminPermission();
$key = hash('sha256', 'invalid-update-price');
$repository = \Mockery::mock(ProductPriceMirrorRepository::class);
$repository->shouldNotReceive('applyOverrides');
$this->app->instance(ProductPriceMirrorRepository::class, $repository);
try {
$this->loginAs()->patch(
route('business.supplier-payables.product-prices.update', ['upstreamKey' => $key]),
[
'barcode' => 'SKU-EDIT',
'purchase_price' => '82.12345',
],
);
$this->fail('超过 4 位小数的价格不应通过编辑。');
} catch (ValidationException $exception) {
$this->assertSame(
'采购价最多支持 4 位小数。',
$exception->errors()['purchase_price'][0],
);
}
}
public function test_import_accepts_workbook_and_reports_created_and_updated_counts(): void
{
$this->grantAdminPermission();
$importer = \Mockery::mock(ProductPriceWorkbookImporter::class);
$importer->shouldReceive('import')
->once()
->withArgs(function (UploadedFile $file, int $companyId, ?int $userId): bool {
return $file->getClientOriginalName() === '商品价格.csv'
&& $companyId === (int) $this->company->id
&& $userId === (int) $this->user->id;
})
->andReturn(['created' => 2, 'updated' => 3, 'rows' => 5]);
$this->app->instance(ProductPriceWorkbookImporter::class, $importer);
$file = UploadedFile::fake()->createWithContent(
'商品价格.csv',
"记录标识,商品编码,采购价\n" . hash('sha256', 'import-row') . ",SKU-1,80\n",
);
$response = $this->loginAs()->post(
route('business.supplier-payables.product-prices.import') . '?barcode=SKU-1',
['import' => $file],
);
$response
->assertRedirect(route('business.pages.show', [
'section' => 'supplier-payables',
'page' => 'product-prices',
]) . '?barcode=SKU-1&page=1')
->assertSessionHas('success', '导入完成:新增维护 2 条,更新维护 3 条。');
}
public function test_template_downloads_an_xlsx_with_product_price_import_headers(): void
{
$this->grantAdminPermission();
$response = $this->loginAs()->get(route(
'business.supplier-payables.product-prices.template',
['company_id' => $this->company->id],
));
$response->assertOk()->assertDownload();
$path = tempnam(sys_get_temp_dir(), 'product-price-template-');
file_put_contents($path, $response->streamedContent());
try {
$spreadsheet = IOFactory::load($path);
$headers = $spreadsheet->getSheet(0)->rangeToArray('A1:K1')[0];
$this->assertSame([
'记录标识', '商品编码', 'SPU', '商品名称', '类目', '生产状态',
'最低售价', '计算限价', '采购价', '运费', '京喜限价',
], $headers);
$this->assertSame('填写说明', $spreadsheet->getSheet(1)->getTitle());
$spreadsheet->disconnectWorksheets();
} finally {
unlink($path);
}
}
}