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
Requires
- php: ^8.2
- illuminate/events: ^10.0 || ^11.0 || ^12.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0
Requires (Dev)
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0
- phpunit/phpunit: ^10.0 || ^11.0
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) orwarning(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
JournalValidatingJournalValidatedJournalRejected
Hook into these with Laravel's event listeners to log, audit, or instrument validation.