illuminatech/balance

提供基于借记和贷记原则的Balance会计系统的支持

1.3.2 2024-03-25 10:42 UTC

This package is auto-updated.

Last update: 2024-09-19 10:38:43 UTC


README

Balance会计系统Laravel扩展


此扩展提供了基于借记和贷记原则的平衡会计系统的基本支持。

有关许可证信息,请查看LICENSE文件。

Latest Stable Version Total Downloads Build Status

安装

安装此扩展的首选方式是通过composer

运行以下命令

php composer.phar require --prefer-dist illuminatech/balance

或者将以下内容添加到您的composer.json文件的require部分。

"illuminatech/balance": "*"

用法

此扩展提供了基于借记和贷记原则的平衡会计系统的基本支持。平衡系统通常用于会计(记账)和资金操作。然而,它也可以用于任何资源从一个位置转移到另一个位置。例如:将商品从仓库转移到商店等。

与平衡系统相关的两个主要术语

  • 账户 - 具有某些逻辑意义的资源的虚拟存储。
  • 交易 - 代表将资源转移到特定账户或从特定账户转移的实际转移。

假设我们有一个提供用户虚拟货币余额的系统。余额中的货币可以用于购买商品,用户可以通过某些支付网关为余额充值。在这种情况下,每个用户应该有3个虚拟余额账户:'virtual-money'、'payment-gateway'和'purchases'。当用户为虚拟余额充值时,我们的系统应从'payment-gateway'中扣除资金并添加到'virtual-money'。当用户购买商品时,我们的系统应从'virtual-money'中扣除资金并添加到'purchases'。关键是:如果你将所有与用户相关的账户的当前金额加起来('payment-gateway' + 'virtual-money' + 'purchases'),它总是等于零。这种检查允许你在任何时候验证是否有错误发生。

此扩展引入了“平衡管理器”术语作为服务,该服务应处理所有平衡交易。此类管理器的公共契约由\Illuminatech\Balance\BalanceContract接口确定。以下是一些特定实现

请参阅特定管理器类以获取更多详细信息。

此扩展提供了\Illuminatech\Balance\BalanceServiceProvider服务提供者,该提供者将\Illuminatech\Balance\BalanceContract绑定到DI容器中的单例。因此,您可以通过自动DI注入或通过容器实例获取平衡管理器。例如

<?php

use Illuminate\Container\Container;
use App\Http\Controllers\Controller;
use Illuminatech\Balance\BalanceContract;

class BalanceController extends Controller
{
    public function increase(BalanceContract $balance, $accountId, $amount)
    {
        $balance->increase($accountId, $amount);
        
        // ...
    }
    
    public function decrease($accountId, $amount)
    {
        $balance = Container::getInstance()->get(BalanceContract::class);
        
        $balance->decrease($accountId, $amount);
        
        // ...
    }
    
    // ...
}

您还可以使用\Illuminatech\Balance\Facades\Balance外观。例如

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::increase($accountId, $amount);

在这些文档中,外观用于代码片段以简化。

应用程序配置

此扩展使用illuminatech/array-factory进行配置。在配置此扩展之前,请确保您熟悉“数组工厂”概念。配置存储在'config/balance.php'文件中。

您可以使用以下控制台命令发布预定义的配置文件

php artisan vendor:publish --provider="Illuminatech\Balance\BalanceServiceProvider" --tag=config

如果您正在使用\Illuminatech\Balance\BalanceDb,您可以使用以下控制台命令发布针对它的预定义数据库迁移

php artisan vendor:publish --provider="Illuminatech\Balance\BalanceServiceProvider" --tag=migrations

基本操作

为了增加特定账户的(借方)余额,使用 \Illuminatech\Balance\BalanceContract::increase() 方法

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::increase($accountId, 500); // add 500 credits to account

为了减少特定账户的(贷方)余额,使用 \Illuminatech\Balance\BalanceContract:decrease() 方法

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::decrease($accountId, 100); // remove 100 credits from account

提示:实际上,方法 decrease() 是多余的,你可以通过调用 increase() 并传入负金额来达到相同的效果。

在您的应用中,不太可能直接使用 increase()decrease() 方法。在大多数情况下,需要一次性将资金从某个账户转移到另一个账户。可以使用 \Illuminatech\Balance\BalanceContract::transfer() 方法来实现。

<?php

use Illuminatech\Balance\Facades\Balance;

$fromId = 1;
$toId = 2;
Balance::transfer($fromId, $toId, 100); // remove 100 credits from account 1 and add 100 credits to account 2

注意:方法 transfer() 会创建两个独立的交易:每个受影响的账户一个。因此,您可以轻松地获取特定账户的所有转账历史,只需选择与其相关的所有交易即可。借方交易将具有正金额,而贷方交易将具有负金额。

注意:如果您希望 transfer() 创建的交易记录另一个参与过程的账户,您需要设置 \Illuminatech\Balance\Balance::$extraAccountLinkAttribute

您可以使用 \Illuminatech\Balance\BalanceContract::revert() 方法回滚特定交易

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::revert($transactionId);

此方法不会删除原始交易,而是创建一个新的交易,以补偿它。

查询账户

使用账户 ID 对余额管理器来说并不实用。在我们的上述示例中,每个系统用户都有 3 个虚拟账户,每个账户都有其唯一的 ID。然而,在执行购买时,我们操作用户 ID 和账户类型,因此在使用余额管理器之前,我们需要查询实际的账户 ID。因此,可以通过设置它们的属性集来指定用于余额管理器方法的账户。例如

<?php

use Illuminatech\Balance\Facades\Balance;

$user = request()->user();

Balance::transfer(
    [
        'userId' => $user->id,
        'type' => 'virtual-money',
    ],
    [
        'userId' => $user->id,
        'type' => 'purchases',
    ],
    500
);

在此示例中,余额管理器将自动使用提供的属性作为过滤器来找到受影响账户的 ID。

您可以通过启用 \Illuminatech\Balance\Balance::$autoCreateAccount 来允许自动创建缺失的账户,如果它们被指定为属性集。这允许在需要时动态创建账户,从而消除预先创建的需求。

请注意!实际上,'账户' 实体在余额系统中是多余的,其使用可以避免。然而,它的存在提供了更多的灵活性并节省了性能。存储账户数据对于此扩展不是必需的,您可以按照不使用它的方式配置您的余额管理器。

查找账户当前余额

特定账户的当前金额始终可以计算为相关交易金额的总和。您可以使用 \Illuminatech\Balance\BalanceContract::calculateBalance() 方法来实现。

<?php

use Illuminatech\Balance\Facades\Balance;

Balance::transfer($fromAccount, $toAccount, 100); // assume this is first time accounts are affected

echo Balance::calculateBalance($fromAccount); // outputs: -100
echo Balance::calculateBalance($toAccount); // outputs: 100

然而,每次需要时都计算当前余额并不高效。因此,您可以指定账户实体的一个属性,该属性将用于存储当前账户余额。这可以通过 \Illuminatech\Balance\Balance::$accountBalanceAttribute 来完成。每次余额管理器执行交易时,它将相应地更新此属性。

<?php

use Illuminate\Support\Facades\DB;
use Illuminatech\Balance\Facades\Balance;

Balance::transfer($fromAccountId, $toAccountId, 100); // assume this is first time accounts are affected

$currentBalance = DB::table('balance_accounts')
    ->select(['balance'])
    ->where(['id' => $fromAccountId])
    ->value('balance');

echo $currentBalance; // outputs: -100

保存额外的交易数据

通常有必要在交易中保存额外信息。例如:我们可能需要保存从支付网关接收到的支付 ID。这可以通过以下方式实现

<?php

use Illuminatech\Balance\Facades\Balance;

$user = request()->user();

// simple increase :
Balance::increase(
    [
        'userId' => $user->id,
        'type' => 'virtual-money',
    ],
    100,
    // extra data associated with transaction :
    [
        'paymentGateway' => 'PayPal',
        'paymentId' => 'abcxyzerft',
    ]
);

// transfer :
Balance::transfer(
    [
        'userId' => $user->id,
        'type' => 'payment-gateway',
    ],
    [
        'userId' => $user->id,
        'type' => 'virtual-money',
    ],
    100,
    // extra data associated with transaction :
    [
        'paymentGateway' => 'PayPal',
        'paymentId' => 'abcxyzerft',
    ]
);

额外属性在数据存储中的存储方式取决于特定的余额管理器实现。例如:\Illuminatech\Balance\BalanceDb 将尝试将额外数据存储在交易表的列中,如果它们的名称等于参数名称。您也可以通过设置特殊的数据字段来设置 \Illuminatech\Balance\BalanceDb::$dataAttribute,它将以序列化的状态存储所有没有匹配列的额外参数。

注意:注意您在交易数据中使用的键:确保它们不会与其他用途保留的列(如主键)冲突。

每笔交易保存余额金额

在会计(簿记)中,有一种常见的做法是记录每笔完成的交易的新的余额金额。这种方法简化了余额转移动态的重建和查找可能的错误。您可以通过设置\Illuminatech\Balance\Balance::$newBalanceAttribute的值,使用交易实体的属性名称,该属性应在交易完成后存储账户余额,来实现这种行为。例如

<?php

use Illuminate\Support\Facades\DB;
use Illuminatech\Balance\Facades\Balance;

$accountId = 1;

$lastTransactionQuery = DB::table('balance_transactions')
    ->where(['account_id' => $accountId])
    ->orderBy('id', 'DESC');

Balance::increase($accountId, 50); // assume this is first time accounts is affected
$lastTransaction = $lastTransactionQuery->first();
echo $lastTransaction->new_balance; // outputs: 50

Balance::increase($accountId, 25);
$lastTransaction = $lastTransactionQuery->first();
echo $lastTransaction->new_balance; // outputs: 75

Balance::decrease($accountId, 50);
$lastTransaction = $lastTransactionQuery->first();
echo $lastTransaction->new_balance; // outputs: 25

事件

\Illuminatech\Balance\Balance提供了一些事件,可以通过事件监听器来处理

例如

<?php

use Illuminate\Support\Facades\Event;
use Illuminatech\Balance\Facades\Balance;
use Illuminatech\Balance\Events\TransactionCreated;
use Illuminatech\Balance\Events\CreatingTransaction;

Event::listen(CreatingTransaction::class, function (CreatingTransaction $event) {
    $event->data['amount'] += 10; // you may adjust transaction data to be saved, including transaction amount
    $event->data['comment'] = 'adjusted by event handler';
});

Event::listen(TransactionCreated::class, function (TransactionCreated $event) {
    echo 'new transaction: '.$event->transactionId; // you may get newly created transaction ID
});

Balance::increase(1, 100); // outputs: 'new transaction: 1'
echo Balance::calculateBalance(1); // outputs: 110