Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
RecordExpenseHandler
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
2 / 2
2
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Expense\Application\Command;
6
7use App\Expense\Domain\Category;
8use App\Expense\Domain\Expense;
9use App\Expense\Domain\ExpenseRepository;
10use App\Expense\Domain\ValueObject\ExpenseId;
11use App\Expense\Domain\ValueObject\TripId;
12use App\Shared\Domain\ValueObject\Currency;
13use App\Shared\Domain\ValueObject\IsoDate;
14use App\Shared\Domain\ValueObject\Money;
15use App\Trip\Application\TripAccess;
16use App\Trip\Domain\ValueObject\TripId as TripLookupId;
17use InvalidArgumentException;
18
19final readonly class RecordExpenseHandler
20{
21    public function __construct(
22        private ExpenseRepository $expenses,
23        private TripAccess $access,
24    ) {
25    }
26
27    public function handle(RecordExpenseCommand $command): ExpenseId
28    {
29        // Owner/editor only: a missing/foreign trip → TripNotFound (404), a viewer → TripAccessDenied (403).
30        $trip = $this->access->getForExpenseWrite(
31            new TripLookupId($command->tripId),
32            $command->userId,
33        );
34
35        $category = Category::tryFrom($command->category)
36            ?? throw new InvalidArgumentException(sprintf('Unknown expense category <%s>.', $command->category));
37
38        $amount = new Money($command->amount, new Currency($command->currency));
39
40        $expense = Expense::record(
41            ExpenseId::generate(),
42            new TripId($command->tripId),
43            $trip->currency(),
44            $amount,
45            $category,
46            $command->description,
47            IsoDate::parse($command->spentAt),
48        );
49
50        $this->expenses->save($expense);
51
52        return $expense->id();
53    }
54}