phpvms/app/Repositories/JournalRepository.php
Arthur Parienté b2bc7b0900
[8.x] refactor: upgrade pint (#2152)
* refactor: upgrade pint

* exclude modules from pint

* apply ai suggestions
2026-03-14 13:34:18 -05:00

272 lines
7.6 KiB
PHP

<?php
namespace App\Repositories;
use App\Contracts\Model;
use App\Contracts\Repository;
use App\Models\Journal;
use App\Models\JournalTransaction;
use App\Models\User;
use App\Support\Money;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use InvalidArgumentException;
use Prettus\Repository\Contracts\CacheableInterface;
use Prettus\Repository\Traits\CacheableRepository;
use Prettus\Validator\Exceptions\ValidatorException;
use UnexpectedValueException;
/**
* Class JournalRepository
*/
class JournalRepository extends Repository implements CacheableInterface
{
use CacheableRepository;
/**
* @return string
*/
public function model()
{
return JournalTransaction::class;
}
/**
* Return a Y-m-d string for the post date
*/
public function formatPostDate(?Carbon $date = null): ?string
{
if (!$date instanceof Carbon) {
return null;
}
return $date->setTimezone('UTC')->toDateString();
}
/**
* Recalculate the balance of the given journal
*
*
* @return Journal
*
* @throws UnexpectedValueException
* @throws InvalidArgumentException
*/
public function recalculateBalance(Journal $journal)
{
$where = [
'journal_id' => $journal->id,
];
$credits = Money::create($this->where($where)->sum('credit') ?: 0);
$debits = Money::create($this->where($where)->sum('debit') ?: 0);
$balance = $credits->subtract($debits);
$journal->balance = $balance->getAmount();
$journal->save();
return $journal;
}
/**
* Post a new transaction to a journal, and also adjust the balance
* on the transaction itself. A cron will run to reconcile the journal
* balance nightly, since they're not atomic operations
*
* @param Money|null $credit Amount to credit
* @param Money|null $debit Amount to debit
* @param Model|null $reference The object this is a reference to
* @param string|null $memo Memo for this transaction
* @param string|null $post_date Date of the posting
* @param string|null $transaction_group Grouping name for the summaries
* @param array|string|null $tags Tag used for grouping/finding items
* @return mixed
*
* @throws ValidatorException
*/
public function post(
Journal &$journal,
?Money $credit = null,
?Money $debit = null,
$reference = null,
$memo = null,
$post_date = null,
$transaction_group = null,
$tags = null
) {
// tags can be passed in a list
if ($tags && \is_array($tags)) {
$tags = implode(',', $tags);
}
if (!$post_date) {
$post_date = Carbon::now('UTC');
}
$attrs = [
'journal_id' => $journal->id,
'credit' => $credit instanceof Money ? $credit->getAmount() : null,
'debit' => $debit instanceof Money ? $debit->getAmount() : null,
'currency' => setting('units.currency', 'USD'),
'memo' => $memo,
'post_date' => $post_date,
'transaction_group' => $transaction_group,
'tags' => $tags,
];
if ($reference !== null) {
$attrs['ref_model_type'] = \get_class($reference);
$attrs['ref_model_id'] = $reference->id;
}
try {
$transaction = $this->create($attrs);
} catch (ValidatorException $e) {
throw $e;
}
$journal->refresh();
return $transaction;
}
/**
* @return Money
*
* @throws UnexpectedValueException
* @throws InvalidArgumentException
*/
public function getBalance(?Journal $journal = null, ?Carbon $date = null)
{
$journal->refresh();
if (!$date instanceof Carbon) {
$date = Carbon::now('UTC');
}
$credit = $this->getCreditBalanceBetween($date, $journal);
$debit = $this->getDebitBalanceBetween($date, $journal);
return $credit->subtract($debit);
}
/**
* Get the credit only balance of the journal based on a given date.
*
* @throws UnexpectedValueException
* @throws InvalidArgumentException
*/
public function getCreditBalanceBetween(
Carbon $date,
?Journal $journal = null,
?Carbon $start_date = null,
?string $transaction_group = null
): Money {
$where = [];
if ($journal instanceof Journal) {
$where['journal_id'] = $journal->id;
}
if ($transaction_group) {
$where['transaction_group'] = $transaction_group;
}
$query = JournalTransaction::where($where);
$query = $query->whereDate('post_date', '<=', $date->toDateString());
if ($start_date instanceof Carbon) {
$query = $query->whereDate('post_date', '>=', $start_date->toDateString());
}
$balance = $query->sum('credit') ?: 0;
return new Money($balance);
}
/**
* @throws UnexpectedValueException
* @throws InvalidArgumentException
*/
public function getDebitBalanceBetween(
Carbon $date,
?Journal $journal = null,
?Carbon $start_date = null,
?string $transaction_group = null
): Money {
$where = [];
if ($journal instanceof Journal) {
$where['journal_id'] = $journal->id;
}
if ($transaction_group) {
$where['transaction_group'] = $transaction_group;
}
$query = JournalTransaction::where($where);
$query = $query->whereDate('post_date', '<=', $date->toDateString());
if ($start_date instanceof Carbon) {
$query = $query->whereDate('post_date', '>=', $start_date->toDateString());
}
$balance = $query->sum('debit') ?: 0;
return new Money($balance);
}
/**
* Return all transactions for a given object
*
* @return array{'credits': Money, 'debits': Money, 'transactions': Collection<int, JournalTransaction>}
*
* @throws UnexpectedValueException
* @throws InvalidArgumentException
*/
public function getAllForObject(Model|User $object, ?Journal $journal = null, ?Carbon $date = null): array
{
$where = [
'ref_model_type' => \get_class($object),
'ref_model_id' => $object->id,
];
if ($journal instanceof Journal) {
$where['journal_id'] = $journal->id;
}
if ($date instanceof Carbon) {
$date = $this->formatPostDate($date);
$where[] = ['post_date', '=', $date];
}
$transactions = $this->whereOrder($where, [
'credit' => 'desc',
'debit' => 'desc',
])->get();
return [
'credits' => new Money($transactions->sum('credit')),
'debits' => new Money($transactions->sum('debit')),
'transactions' => $transactions,
];
}
/**
* Delete all transactions for a given object
*/
public function deleteAllForObject(Model $object, ?Journal $journal = null): void
{
$where = [
'ref_model_type' => \get_class($object),
'ref_model_id' => $object->id,
];
if ($journal instanceof Journal) {
$where['journal_id'] = $journal->id;
}
$this->deleteWhere($where);
}
}