195 lines
9.2 KiB
PHP
195 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Services\FinanceLedgerCalculator;
|
|
use Tests\TestCase;
|
|
|
|
class FinanceLedgerCalculatorTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
'finance_ledger.opening_date' => '2026-10-17',
|
|
]);
|
|
DB::purge('business_mirror');
|
|
$schema = Schema::connection('business_mirror');
|
|
|
|
$schema->create('fin_accounts', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('company_name');
|
|
$table->string('account_short');
|
|
$table->decimal('current_balance', 18, 2)->default(0);
|
|
$table->decimal('blocked_amount', 18, 2)->default(0);
|
|
$table->decimal('daily_available', 18, 2)->default(0);
|
|
$table->timestamp('updated_at')->nullable();
|
|
});
|
|
$schema->create('fin_account_opening', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('account_short');
|
|
$table->date('opening_date');
|
|
$table->decimal('opening_balance', 18, 2)->default(0);
|
|
$table->timestamp('updated_at')->nullable();
|
|
});
|
|
$schema->create('fin_account_statuses', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('account_short')->unique();
|
|
$table->string('status');
|
|
$table->string('updated_by')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
$schema->create('fin_transactions', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->date('txn_date');
|
|
$table->string('account_short')->nullable();
|
|
$table->string('source');
|
|
$table->string('source_value')->nullable();
|
|
$table->decimal('income', 18, 2)->default(0);
|
|
$table->decimal('expense', 18, 2)->default(0);
|
|
});
|
|
$schema->create('fin_account_map', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('source');
|
|
$table->string('source_value');
|
|
$table->string('account_short')->nullable();
|
|
});
|
|
$schema->create('fin_daily_flows', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->date('flow_date');
|
|
$table->string('company_name')->nullable();
|
|
$table->string('account_short');
|
|
$table->decimal('opening_balance', 18, 2)->default(0);
|
|
$table->decimal('inflow', 18, 2)->default(0);
|
|
$table->decimal('outflow', 18, 2)->default(0);
|
|
$table->decimal('closing_balance', 18, 2)->default(0);
|
|
$table->string('remark')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
$schema->create('fin_shops', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('company_name');
|
|
$table->decimal('shop_balance', 18, 2)->default(0);
|
|
$table->timestamp('updated_at')->nullable();
|
|
});
|
|
|
|
$db = DB::connection('business_mirror');
|
|
$db->table('fin_accounts')->insert([
|
|
['company_name' => '公司A', 'account_short' => '账户A', 'blocked_amount' => 0],
|
|
['company_name' => '公司B', 'account_short' => '账户B', 'blocked_amount' => 0],
|
|
]);
|
|
$db->table('fin_account_opening')->insert([
|
|
['account_short' => '账户A', 'opening_date' => '2026-10-17', 'opening_balance' => 1000],
|
|
['account_short' => '账户B', 'opening_date' => '2026-10-17', 'opening_balance' => 500],
|
|
]);
|
|
$db->table('fin_account_statuses')->insert([
|
|
['account_short' => '账户A', 'status' => 'normal', 'updated_by' => 'Test User'],
|
|
['account_short' => '账户B', 'status' => 'limited', 'updated_by' => 'Test User'],
|
|
]);
|
|
$db->table('fin_transactions')->insert([
|
|
['txn_date' => '2026-10-17', 'account_short' => '', 'source' => 'shop', 'source_value' => '店铺A', 'income' => 100, 'expense' => 0],
|
|
['txn_date' => '2026-10-18', 'account_short' => '账户A', 'source' => 'shop', 'source_value' => '店铺A', 'income' => 0, 'expense' => 50],
|
|
]);
|
|
$db->table('fin_account_map')->insert([
|
|
['source' => 'shop', 'source_value' => '店铺A', 'account_short' => '账户A'],
|
|
]);
|
|
$db->table('fin_shops')->insert([
|
|
['company_name' => '公司A', 'shop_balance' => 0],
|
|
]);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
DB::purge('business_mirror');
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_daily_flows_roll_forward_from_opening_and_merge_opening_day(): void
|
|
{
|
|
$result = app(FinanceLedgerCalculator::class)->recalculate();
|
|
|
|
$rows = DB::connection('business_mirror')->table('fin_daily_flows')
|
|
->orderBy('account_short')->orderBy('flow_date')->get();
|
|
|
|
$this->assertSame(3, $result['flows']);
|
|
$this->assertSame(['账户A', '账户A', '账户B'], $rows->pluck('account_short')->all());
|
|
$this->assertEqualsWithDelta(1000.00, (float) $rows[0]->opening_balance, 0.001);
|
|
$this->assertEqualsWithDelta(100.00, (float) $rows[0]->inflow, 0.001);
|
|
$this->assertEqualsWithDelta(1100.00, (float) $rows[0]->closing_balance, 0.001);
|
|
$this->assertEqualsWithDelta(1100.00, (float) $rows[1]->opening_balance, 0.001);
|
|
$this->assertEqualsWithDelta(50.00, (float) $rows[1]->outflow, 0.001);
|
|
$this->assertEqualsWithDelta(1050.00, (float) $rows[1]->closing_balance, 0.001);
|
|
$this->assertEqualsWithDelta(500.00, (float) $rows[2]->closing_balance, 0.001);
|
|
|
|
$accounts = DB::connection('business_mirror')->table('fin_accounts')->get()->keyBy('account_short');
|
|
$this->assertEqualsWithDelta(1050.00, (float) $accounts['账户A']->current_balance, 0.001);
|
|
$this->assertEqualsWithDelta(1050.00, (float) $accounts['账户A']->daily_available, 0.001);
|
|
$this->assertEqualsWithDelta(0.00, (float) $accounts['账户A']->blocked_amount, 0.001);
|
|
$this->assertEqualsWithDelta(500.00, (float) $accounts['账户B']->current_balance, 0.001);
|
|
$this->assertEqualsWithDelta(0.00, (float) $accounts['账户B']->daily_available, 0.001);
|
|
$this->assertEqualsWithDelta(500.00, (float) $accounts['账户B']->blocked_amount, 0.001);
|
|
$this->assertEqualsWithDelta(1050.00, (float) DB::connection('business_mirror')->table('fin_shops')->value('shop_balance'), 0.001);
|
|
}
|
|
|
|
public function test_account_amounts_follow_status_changes(): void
|
|
{
|
|
$calculator = app(FinanceLedgerCalculator::class);
|
|
$calculator->recalculate();
|
|
|
|
DB::connection('business_mirror')->table('fin_account_statuses')
|
|
->where('account_short', '账户B')
|
|
->update(['status' => 'normal', 'updated_by' => 'Test User']);
|
|
$calculator->refreshAccountAmounts('账户B');
|
|
|
|
$account = DB::connection('business_mirror')->table('fin_accounts')
|
|
->where('account_short', '账户B')
|
|
->first();
|
|
$this->assertEqualsWithDelta(500.00, (float) $account->daily_available, 0.001);
|
|
$this->assertEqualsWithDelta(0.00, (float) $account->blocked_amount, 0.001);
|
|
}
|
|
|
|
public function test_new_account_uses_its_later_opening_date_and_ignores_earlier_transactions(): void
|
|
{
|
|
$db = DB::connection('business_mirror');
|
|
$db->table('fin_accounts')->insert([
|
|
'company_name' => '新设公司',
|
|
'account_short' => '新设账户',
|
|
'blocked_amount' => 0,
|
|
]);
|
|
$db->table('fin_account_opening')->insert([
|
|
'account_short' => '新设账户',
|
|
'opening_date' => '2026-10-20',
|
|
'opening_balance' => 700,
|
|
]);
|
|
$db->table('fin_transactions')->insert([
|
|
['txn_date' => '2026-10-18', 'account_short' => '新设账户', 'source' => 'manual', 'source_value' => null, 'income' => 999, 'expense' => 0],
|
|
['txn_date' => '2026-10-20', 'account_short' => '新设账户', 'source' => 'manual', 'source_value' => null, 'income' => 200, 'expense' => 0],
|
|
]);
|
|
|
|
app(FinanceLedgerCalculator::class)->recalculate();
|
|
|
|
$flows = $db->table('fin_daily_flows')
|
|
->where('account_short', '新设账户')
|
|
->orderBy('flow_date')
|
|
->get();
|
|
$this->assertCount(1, $flows);
|
|
$this->assertSame('2026-10-20', (string) $flows[0]->flow_date);
|
|
$this->assertEqualsWithDelta(700.00, (float) $flows[0]->opening_balance, 0.001);
|
|
$this->assertEqualsWithDelta(200.00, (float) $flows[0]->inflow, 0.001);
|
|
$this->assertEqualsWithDelta(900.00, (float) $flows[0]->closing_balance, 0.001);
|
|
$this->assertEqualsWithDelta(
|
|
900.00,
|
|
(float) $db->table('fin_accounts')->where('account_short', '新设账户')->value('current_balance'),
|
|
0.001,
|
|
);
|
|
}
|
|
}
|