CaiWuXiTong/tests/Feature/Console/VipshopMpBillImporterTest.php
qiuxiang 37d4cb06a0 feat: 导入唯品会MP后台月度账单
新增唯品会MP明细导入命令,支持店铺名称映射和按账期幂等替换;补充导入及页面回归测试。
2026-11-06 17:17:48 +08:00

176 lines
7.3 KiB
PHP

<?php
namespace Tests\Feature\Console;
use Illuminate\Support\Facades\DB;
use Modules\EcommerceFinance\Models\AccountBill;
use Modules\EcommerceFinance\Services\VipshopMpBillImporter;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use InvalidArgumentException;
use Tests\TestCase;
class VipshopMpBillImporterTest extends TestCase
{
private const HEADERS = [
'全局ID', '店铺ID', '店铺名称', '订单签收或退款时间', '订单类型名称', '行类型名称',
'售后类型名称', '售后单号', '订单编号', 'SKU', '商品款号', '商品SKU', '商品描述',
'店铺协议编号', '二级类目', '三级类目', '商品单价', '商品数量', '商品金额(A)',
'商家优惠(B)', '调整金额(C)', '无需退款金额(D)', '总金额(E=A-B-C-D)', '扣点',
'技术服务费(F)', '海淘综合税(G)', '分享金扣点', '唯品仓分享金', '商家应收(G=E-F-G)',
];
private array $files = [];
private array $directories = [];
protected function tearDown(): void
{
foreach ($this->files as $path) {
@unlink($path);
}
foreach ($this->directories as $directory) {
@rmdir($directory);
}
parent::tearDown();
}
public function test_it_imports_exported_details_with_store_mapping_and_replaces_only_the_period(): void
{
$companyId = (int) DB::table('companies')->value('id');
$this->existingBill('OLD-TARGET', '唯品会MP-亿家达家具旗舰店', '2026-10');
$this->existingBill('KEEP-PERIOD', '唯品会MP-亿家达家具旗舰店', '2026-09');
$this->existingBill('KEEP-STORE', '唯品会MP-卓禾家具特卖旗舰店', '2026-10');
$first = $this->xlsx(
'vip-mp-yijiada_MP-ST96730107-20261101-01.xlsx',
[$this->row('REC-INCOME', '亿家达家具特卖旗舰店', '100', '10', '90')],
);
$second = $this->xlsx(
'vip-mp-pumei_MP-ST86337204-20261101-01.xlsx',
[$this->row('REC-EXPENSE', '朴美家具特卖旗舰店', '-50', '-5', '-45')],
);
$result = app(VipshopMpBillImporter::class)->import(
[$first, $second],
'2026-10',
[
'亿家达家具特卖旗舰店' => '唯品会MP-亿家达家具旗舰店',
'朴美家具特卖旗舰店' => '唯品会MP-朴美家具特卖旗舰店',
],
$companyId,
);
$this->assertSame('succeeded', $result['status']);
$this->assertSame(2, $result['row_count']);
$this->assertSame(2, $result['store_count']);
$this->assertSame(2, $result['bill_count']);
$this->assertSame('110.000000', $result['income_total']);
$this->assertSame('55.000000', $result['expense_total']);
$this->assertSame('45.000000', $result['balance_total']);
$this->assertDatabaseMissing('ec_account_bills', ['record_code' => 'OLD-TARGET']);
$this->assertDatabaseHas('ec_account_bills', ['record_code' => 'KEEP-PERIOD']);
$this->assertDatabaseHas('ec_account_bills', ['record_code' => 'KEEP-STORE']);
$row = AccountBill::query()->where('record_code', 'REC-EXPENSE')->firstOrFail();
$this->assertSame('唯品会MP-朴美家具特卖旗舰店', $row->store_name);
$this->assertSame('2026-10', $row->payload['settle_period']);
$this->assertSame('vip-mp-pumei_MP-ST86337204-20261101-01.xlsx', $row->payload['source_file']);
$this->assertSame(-55.0, (float) $row->amount);
$this->assertSame(55.0, (float) $row->expense);
$again = app(VipshopMpBillImporter::class)->import(
[$first, $second],
'2026-10',
[
'亿家达家具特卖旗舰店' => '唯品会MP-亿家达家具旗舰店',
'朴美家具特卖旗舰店' => '唯品会MP-朴美家具特卖旗舰店',
],
$companyId,
);
$this->assertSame('succeeded', $again['status']);
$this->assertSame(2, AccountBill::query()->whereIn('record_code', ['REC-INCOME', 'REC-EXPENSE'])->count());
}
public function test_duplicate_record_codes_fail_without_changing_existing_period(): void
{
$companyId = (int) DB::table('companies')->value('id');
$this->existingBill('ACTIVE', '唯品会MP-亿家达家具旗舰店', '2026-10');
$first = $this->xlsx(
'vip-mp-one_MP-ST96730107-20261101-01.xlsx',
[$this->row('DUPLICATE', '亿家达家具特卖旗舰店', '10', '0', '10')],
);
$second = $this->xlsx(
'vip-mp-two_MP-ST86337204-20261101-01.xlsx',
[$this->row('DUPLICATE', '朴美家具特卖旗舰店', '20', '0', '20')],
);
try {
app(VipshopMpBillImporter::class)->import(
[$first, $second],
'2026-10',
[
'亿家达家具特卖旗舰店' => '唯品会MP-亿家达家具旗舰店',
'朴美家具特卖旗舰店' => '唯品会MP-朴美家具特卖旗舰店',
],
$companyId,
);
$this->fail('重复全局ID必须拒绝导入。');
} catch (InvalidArgumentException $exception) {
$this->assertStringContainsString('重复', $exception->getMessage());
}
$this->assertDatabaseHas('ec_account_bills', ['record_code' => 'ACTIVE']);
$this->assertDatabaseMissing('ec_account_bills', ['record_code' => 'DUPLICATE']);
}
private function row(
string $recordCode,
string $storeName,
string $total,
string $technicalFee,
string $receivable,
): array {
return [
$recordCode, 'ST00000001', $storeName, '2026-10-15 10:00:00', '销售订单', '货款',
'', '', '26010100000001', 'SKU-1', 'ITEM-1', 'SKU-1', '', 'AGREEMENT-1',
'', '书柜', '100', '1', $total, '0', '0', '0', $total, '10.00%',
$technicalFee, '0', '0.00%', '0', $receivable,
];
}
private function xlsx(string $name, array $rows): string
{
$spreadsheet = new Spreadsheet();
$spreadsheet->getActiveSheet()->setTitle('订单明细');
$spreadsheet->getActiveSheet()->fromArray(array_merge([self::HEADERS], $rows));
$directory = sys_get_temp_dir() . '/vipshop-mp-' . uniqid('', true);
mkdir($directory, 0700, true);
$this->directories[] = $directory;
$path = $directory . '/' . $name;
(new Xlsx($spreadsheet))->save($path);
$spreadsheet->disconnectWorksheets();
$this->files[] = $path;
return $path;
}
private function existingBill(string $recordCode, string $storeName, string $period): void
{
DB::table('ec_account_bills')->insert([
'company_id' => (int) DB::table('companies')->value('id'),
'bill_type' => 'vipshop',
'record_code' => $recordCode,
'store_name' => $storeName,
'order_no' => 'OLD',
'bill_item' => '原项目',
'occurred_at' => '2026-10-15 00:00:00',
'income' => 1,
'expense' => 0,
'balance' => 0,
'payload' => json_encode(['settle_period' => $period], JSON_THROW_ON_ERROR),
'created_at' => now(),
'updated_at' => now(),
]);
}
}