meita/journal-guard

Guard and validate journal entries before they are posted to the ledger.

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/meita/journal-guard

1.0.0 2025-12-21 06:58 UTC

This package is auto-updated.

Last update: 2025-12-21 07:03:08 UTC


README

Guard and validate journal entries before they are posted to the ledger. Built for Laravel 10/11/12 with extensible rule classes and clear, human-readable errors.

Installation

composer require meita/journal-guard
php artisan vendor:publish --provider="Meita\\JournalGuard\\JournalGuardServiceProvider" --tag=config

Configuration

config/journal-guard.php ships with:

  • enabled: toggle the guard.
  • mode: strict (throws) or warning (returns warnings, no throw).
  • rules: ordered list of rule classes.
  • account_model, period_model, currency_model: optional model classes for built-in rules.
  • resolvers.account|period|currency: optional callbacks to resolve accounts/periods/currency without coupling to models.
  • default_currency, enforce_default_currency: enforce default currency when needed.

Example resolver registration:

app('config')->set('journal-guard.resolvers.account', function ($line, $entry) {
    // return model/array/bool to indicate active account
    return $line->accountId() === 100 ? (object) ['is_active' => true] : null;
});

Usage

Validating a payload

use Meita\JournalGuard\Facades\JournalGuard;
use Meita\JournalGuard\Exceptions\JournalValidationException;

$payload = [
    'company_id' => 1,
    'branch_id' => 2,
    'currency' => 'SAR',
    'entry_date' => '2024-01-31',
    'reference' => 'JV-1001',
    'description' => 'Accruals',
    'lines' => [
        ['account_id' => 10, 'debit' => 1000, 'credit' => 0, 'description' => 'Expense'],
        ['account_id' => 200, 'debit' => 0, 'credit' => 1000, 'description' => 'Accrual'],
    ],
];

try {
    $result = JournalGuard::validate($payload); // returns ValidationResult on success
} catch (JournalValidationException $e) {
    // $e->errors() holds code/message/path tuples
}

Warning mode

config(['journal-guard.mode' => 'warning']);
$result = JournalGuard::validate($payload);

if (!$result->passed()) {
    // $result->warnings() contains non-blocking issues
}

Trait for services

use Meita\JournalGuard\Traits\GuardsJournalEntries;

class JournalEntryService
{
    use GuardsJournalEntries;

    public function create(array $payload)
    {
        $this->guardJournalEntry($payload);
        // ...persist entry
    }
}

Custom rules

Create a rule implementing Meita\JournalGuard\Contracts\RuleInterface and add it to the rules config array.

use Meita\JournalGuard\Contracts\RuleInterface;
use Meita\JournalGuard\DTO\JournalEntryDTO;
use Meita\JournalGuard\Exceptions\JournalValidationException;

class NoWeekendEntriesRule implements RuleInterface
{
    public function validate(JournalEntryDTO $entry): void
    {
        if ($entry->date()->isWeekend()) {
            throw new JournalValidationException([
                ['code' => 'weekend_block', 'message' => 'Weekend entries are not allowed.', 'path' => 'entry_date'],
            ]);
        }
    }
}

Middleware-like usage

public function store(Request $request)
{
    try {
        JournalGuard::validate($request->all());
    } catch (JournalValidationException $e) {
        return back()->withErrors($e->errors());
    }

    // continue to save...
}

Built-in rules

  • BalancedRule: total debit equals total credit; totals must be > 0.
  • PositiveAmountsRule: no negatives, no line with both debit and credit, no empty amounts.
  • AccountsActiveRule: accounts exist and are active (via resolver or configured model).
  • ClosedPeriodRule: entry date falls within an open period (resolver or model).
  • CurrencyRule: currency allowed for the company and default currency enforcement.

Events

  • JournalValidating
  • JournalValidated
  • JournalRejected

Hook into these with Laravel's event listeners to log, audit, or instrument validation.