CaiWuXiTong/tests/Feature/Business/TaobaoWechatReceivingBillsTest.php

246 lines
10 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use Mockery;
use Modules\EcommerceFinance\Repositories\TaobaoWechatOpeningBalanceRepository;
use Modules\EcommerceFinance\Repositories\TaobaoWechatSettlementBillRepository;
use RuntimeException;
use Tests\Feature\FeatureTestCase;
class TaobaoWechatReceivingBillsTest extends FeatureTestCase
{
public function test_endpoint_reads_authoritative_taobao_wechat_rows_with_all_filters(): void
{
$this->allowPage();
$filters = [
'store_name' => '淘系微信-测试店',
'bill_item' => '交易收入',
'start' => '2026-08-12',
'end' => '2026-09-10',
'keyword' => '260909',
];
$repository = Mockery::mock(TaobaoWechatSettlementBillRepository::class);
$repository->shouldReceive('paginateAccountBills')->once()->with(
$filters, 'occurred_at', 'desc', 2, 20,
)->andReturn([
'current_page' => 2,
'last_page' => 3,
'per_page' => 20,
'total' => 41,
'data' => [[
'id' => '14424200',
'record_code' => '380425163199',
'store_name' => '淘系微信-测试店',
'income' => 91.64,
'expense' => 0.0,
'payload' => [
'taobao_order_no' => '260909-047741735284017',
'entry_type' => '收入',
],
]],
'summary' => ['income' => 91.64, 'expense' => 0.0, 'balance' => 91.64, 'count' => 41],
]);
$this->app->instance(TaobaoWechatSettlementBillRepository::class, $repository);
$openingRepository = Mockery::mock(TaobaoWechatOpeningBalanceRepository::class);
$openingRepository->shouldReceive('openingBalance')
->once()
->with('淘系微信-测试店', '2026-08-12')
->andReturn([
'balance' => 230.0,
'date' => '2026-08-11',
'source' => 'merchant_backoffice_june_ending_carried_forward',
'store_count' => 1,
]);
$this->app->instance(TaobaoWechatOpeningBalanceRepository::class, $openingRepository);
$this->withExceptionHandling()
->loginAs()
->getJson(route('ecommerce-finance.account-bills.index', [
'bill_type' => 'taobao-wechat',
...$filters,
'page' => 2,
'limit' => 20,
]))
->assertOk()
->assertJsonPath('data.0.payload.entry_type', '收入')
->assertJsonPath('columns.title', '淘系微信账户')
->assertJsonPath('summary.count', 41)
->assertJsonPath('summary.opening_balance', 230)
->assertJsonPath('summary.opening_balance_date', '2026-08-11')
->assertJsonPath('summary.opening_balance_source', 'merchant_backoffice_june_ending_carried_forward')
->assertJsonPath('summary.opening_balance_store_count', 1)
->assertJsonPath('summary.balance', 321.64)
->assertJsonPath('summary.balance_source', 'opening_plus_period_activity');
}
public function test_july_closing_equals_june_ending_plus_income_minus_expense(): void
{
$this->allowPage();
$filters = [
'start' => '2026-07-01',
'end' => '2026-07-31',
];
$repository = Mockery::mock(TaobaoWechatSettlementBillRepository::class);
$repository->shouldReceive('paginateAccountBills')->once()->andReturn([
'current_page' => 1,
'last_page' => 1,
'per_page' => 20,
'total' => 2,
'data' => [],
'summary' => ['income' => 100.0, 'expense' => 25.5, 'balance' => 74.5, 'count' => 2],
]);
$this->app->instance(TaobaoWechatSettlementBillRepository::class, $repository);
$openingRepository = Mockery::mock(TaobaoWechatOpeningBalanceRepository::class);
$openingRepository->shouldReceive('openingBalance')
->once()
->with(null, '2026-07-01')
->andReturn([
'balance' => 230.0,
'date' => '2026-06-30',
'source' => 'merchant_backoffice_june_ending',
'store_count' => 20,
]);
$this->app->instance(TaobaoWechatOpeningBalanceRepository::class, $openingRepository);
$this->withExceptionHandling()
->loginAs()
->getJson(route('ecommerce-finance.account-bills.index', [
'bill_type' => 'taobao-wechat',
...$filters,
]))
->assertOk()
->assertJsonPath('summary.opening_balance', 230)
->assertJsonPath('summary.opening_balance_date', '2026-06-30')
->assertJsonPath('summary.opening_balance_source', 'merchant_backoffice_june_ending')
->assertJsonPath('summary.opening_balance_store_count', 20)
->assertJsonPath('summary.balance', 304.5)
->assertJsonPath('summary.balance_source', 'opening_plus_period_activity');
}
public function test_balance_stays_unavailable_when_opening_balance_is_not_imported(): void
{
$this->allowPage();
$repository = Mockery::mock(TaobaoWechatSettlementBillRepository::class);
$repository->shouldReceive('paginateAccountBills')->once()->andReturn([
'current_page' => 1,
'last_page' => 1,
'per_page' => 20,
'total' => 1,
'data' => [],
'summary' => ['income' => 10.0, 'expense' => 2.0, 'balance' => 8.0, 'count' => 1],
]);
$this->app->instance(TaobaoWechatSettlementBillRepository::class, $repository);
$openingRepository = Mockery::mock(TaobaoWechatOpeningBalanceRepository::class);
$openingRepository->shouldReceive('openingBalance')->once()->andReturn([
'balance' => null,
'date' => null,
'source' => 'unavailable',
'store_count' => 0,
]);
$this->app->instance(TaobaoWechatOpeningBalanceRepository::class, $openingRepository);
$this->withExceptionHandling()
->loginAs()
->getJson(route('ecommerce-finance.account-bills.index', [
'bill_type' => 'taobao-wechat',
'start' => '2026-11-01',
'end' => '2026-11-30',
]))
->assertOk()
->assertJsonPath('summary.opening_balance', null)
->assertJsonPath('summary.opening_balance_date', null)
->assertJsonPath('summary.opening_balance_source', 'unavailable')
->assertJsonPath('summary.opening_balance_store_count', 0)
->assertJsonPath('summary.balance', null)
->assertJsonPath('summary.balance_source', 'unavailable');
}
public function test_filter_options_and_selected_column_export_use_the_same_mirror(): void
{
$this->allowPage();
$repository = Mockery::mock(TaobaoWechatSettlementBillRepository::class);
$repository->shouldReceive('accountBillFilterOptions')->once()->andReturn([
'shops' => ['淘系微信-A店', '淘系微信-B店'],
'bill_items' => ['交易收入', '技术服务费'],
]);
$repository->shouldReceive('exportAccountBills')->once()->with(
['store_name' => '淘系微信-A店'], 'record_code', 'asc',
)->andReturn([[
'record_code' => '380425163199',
'store_name' => '淘系微信-A店',
'payload' => ['entry_type' => '收入'],
]]);
$this->app->instance(TaobaoWechatSettlementBillRepository::class, $repository);
$this->loginAs()
->withHeader('X-Requested-With', 'XMLHttpRequest')
->get(route('ecommerce-finance.account-bills.filter-options', ['bill_type' => 'taobao-wechat']))
->assertOk()
->assertExactJson([
'shops' => ['淘系微信-A店', '淘系微信-B店'],
'bill_items' => ['交易收入', '技术服务费'],
'ledger_remarks' => [],
]);
$response = $this->loginAs()->get(route('ecommerce-finance.account-bills.export', [
'bill_type' => 'taobao-wechat',
'store_name' => '淘系微信-A店',
'sort' => 'record_code',
'dir' => 'asc',
'columns' => 'payload.entry_type,record_code,store_name',
]));
$response->assertOk()
->assertHeader('content-type', 'text/csv; charset=UTF-8')
->assertDownload('taobao-wechat_account_bills.csv');
$csv = $response->streamedContent();
$this->assertStringContainsString('入账类型,记录编码,店铺', $csv);
$this->assertStringContainsString('收入,"=""380425163199""",淘系微信-A店', $csv);
}
public function test_unready_mirror_returns_a_clear_retryable_response(): void
{
$this->allowPage();
$repository = Mockery::mock(TaobaoWechatSettlementBillRepository::class);
$repository->shouldReceive('paginateAccountBills')->once()->andThrow(
new RuntimeException('taobao_wechat_authoritative_bills_not_ready')
);
$this->app->instance(TaobaoWechatSettlementBillRepository::class, $repository);
$this->loginAs()
->withHeader('X-Requested-With', 'XMLHttpRequest')
->get(route('ecommerce-finance.account-bills.index', ['bill_type' => 'taobao-wechat']))
->assertStatus(503)
->assertJsonPath('message', '淘系微信完整账单尚未完成首次导入,当前不会回退到不完整的聚水潭辅助表。');
}
public function test_invalid_date_range_is_rejected_before_reading_the_mirror(): void
{
$this->allowPage();
$repository = Mockery::mock(TaobaoWechatSettlementBillRepository::class);
$repository->shouldNotReceive('paginateAccountBills');
$this->app->instance(TaobaoWechatSettlementBillRepository::class, $repository);
$this->withExceptionHandling()
->loginAs()
->getJson(route('ecommerce-finance.account-bills.index', [
'bill_type' => 'taobao-wechat',
'start' => '2026-09-10',
'end' => '2026-08-12',
]))
->assertStatus(422);
}
private function allowPage(): void
{
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
}