diff --git a/app/Commands/Review.php b/app/Commands/Review.php index 11b7e72..8c39b63 100644 --- a/app/Commands/Review.php +++ b/app/Commands/Review.php @@ -6,6 +6,7 @@ use Domain\Aggregates\TaxYear\Repositories\TaxYearSummaryRepository; use Domain\Aggregates\TaxYear\ValueObjects\Exceptions\TaxYearIdException; use Domain\Aggregates\TaxYear\ValueObjects\TaxYearId; +use Domain\Repositories\SummaryRepository; use Illuminate\Database\QueryException; final class Review extends Command @@ -26,7 +27,7 @@ final class Review extends Command protected $description = 'Display a tax year\'s summary'; /** Execute the console command. */ - public function handle(TaxYearSummaryRepository $repository): int + public function handle(SummaryRepository $summaryRepository, TaxYearSummaryRepository $taxYearRepository): int { $taxYear = $this->argument('taxyear'); @@ -41,7 +42,7 @@ public function handle(TaxYearSummaryRepository $repository): int try { $availableTaxYears = array_filter(array_map( fn (mixed $taxYearId) => $taxYearId instanceof TaxYearId ? $taxYearId->toString() : null, - array_column($repository->all(), 'tax_year_id'), + array_column($taxYearRepository->all(), 'tax_year_id'), )); } catch (QueryException) { $availableTaxYears = []; @@ -58,16 +59,20 @@ public function handle(TaxYearSummaryRepository $repository): int $taxYear = null; } + if (is_null($taxYear)) { + $this->presenter->info(sprintf('Current fiat balance: %s', $summaryRepository->get()?->fiat_balance ?? '')); + } + // Order tax years from more recent to older rsort($availableTaxYears); $taxYear ??= count($availableTaxYears) === 1 ? $availableTaxYears[0] - : $this->presenter->choice('Please select a tax year', $availableTaxYears, $availableTaxYears[0]); + : $this->presenter->choice('Please select a tax year for details', $availableTaxYears, $availableTaxYears[0]); assert(is_string($taxYear)); - return $this->summary($repository, $taxYear, $availableTaxYears); + return $this->summary($taxYearRepository, $taxYear, $availableTaxYears); } /** @param list $availableTaxYears */ diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3dfa617..8244cd6 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; +use App\Repositories\SummaryRepository; use App\Services\ActionRunner\ActionRunner; use App\Services\CommandRunner\CommandRunner; use App\Services\CommandRunner\CommandRunnerContract; @@ -13,6 +14,7 @@ use App\Services\TransactionProcessor\TransactionProcessorContract; use App\Services\TransactionReader\Adapters\PhpSpreadsheetAdapter; use App\Services\TransactionReader\TransactionReader; +use Domain\Repositories\SummaryRepository as SummaryRepositoryInterface; use Domain\Services\ActionRunner\ActionRunner as ActionRunnerInterface; use Domain\Services\TransactionDispatcher\TransactionDispatcher; use Domain\Services\TransactionDispatcher\TransactionDispatcherContract; @@ -33,6 +35,8 @@ public function register(): void $this->app->singleton(TransactionProcessorContract::class, fn (Application $app) => resolve(TransactionProcessor::class)); $this->app->singleton(TransactionReader::class, fn (Application $app) => new PhpSpreadsheetAdapter()); + $this->app->bind(SummaryRepositoryInterface::class, SummaryRepository::class); + if (! $this->isProduction()) { $this->app->register(TinkerZeroServiceProvider::class); } diff --git a/app/Repositories/SummaryRepository.php b/app/Repositories/SummaryRepository.php new file mode 100644 index 0000000..c5258d0 --- /dev/null +++ b/app/Repositories/SummaryRepository.php @@ -0,0 +1,16 @@ + (string) $value->date, 'quantity' => (string) $value->quantity, 'cost_basis' => ['quantity' => (string) $value->costBasis->quantity, 'currency' => $value->costBasis->currency->value], + 'for_fiat' => $value->forFiat, 'same_day_quantity' => (string) $value->sameDayQuantity(), 'thirty_day_quantity' => (string) $value->thirtyDayQuantity(), ]; diff --git a/app/Services/ObjectHydration/Hydrators/SharePoolingAssetDisposalHydrator.php b/app/Services/ObjectHydration/Hydrators/SharePoolingAssetDisposalHydrator.php index a6fe088..c5f2068 100644 --- a/app/Services/ObjectHydration/Hydrators/SharePoolingAssetDisposalHydrator.php +++ b/app/Services/ObjectHydration/Hydrators/SharePoolingAssetDisposalHydrator.php @@ -30,6 +30,7 @@ public function cast(mixed $value, ObjectMapper $hydrator): mixed quantity: new Quantity($value['quantity']), costBasis: new FiatAmount($value['cost_basis']['quantity'], FiatCurrency::from($value['cost_basis']['currency'])), proceeds: new FiatAmount($value['proceeds']['quantity'], FiatCurrency::from($value['proceeds']['currency'])), + forFiat: (bool) $value['for_fiat'], sameDayQuantityAllocation: new QuantityAllocation( array_map(fn (string $quantity) => new Quantity($quantity), $value['same_day_quantity_allocation']), ), @@ -50,6 +51,7 @@ public function serialize(mixed $value, ObjectMapper $hydrator): mixed 'quantity' => (string) $value->quantity, 'cost_basis' => ['quantity' => (string) $value->costBasis->quantity, 'currency' => $value->costBasis->currency->value], 'proceeds' => ['quantity' => (string) $value->proceeds->quantity, 'currency' => $value->proceeds->currency->value], + 'for_fiat' => $value->forFiat, 'same_day_quantity_allocation' => $this->serializeQuantityAllocation($value->sameDayQuantityAllocation), 'thirty_day_quantity_allocation' => $this->serializeQuantityAllocation($value->thirtyDayQuantityAllocation), 'processed' => $value->processed, diff --git a/app/Services/Presenter/PresenterContract.php b/app/Services/Presenter/PresenterContract.php index e8c7c22..6911381 100644 --- a/app/Services/Presenter/PresenterContract.php +++ b/app/Services/Presenter/PresenterContract.php @@ -31,7 +31,7 @@ public function summary( string $nonAttributableAllowableCost, string $totalCostBasis, string $capitalGain, - string $income + string $income, ): void; /** Initiate a progress bar. */ diff --git a/database/migrations/2023_08_18_144245_create_summaries_table.php b/database/migrations/2023_08_18_144245_create_summaries_table.php new file mode 100644 index 0000000..2f9b7b9 --- /dev/null +++ b/database/migrations/2023_08_18_144245_create_summaries_table.php @@ -0,0 +1,23 @@ +id(); + $table->string('currency', 3); + $table->string('fiat_balance')->default('0'); + }); + } + + /** Reverse the migrations. */ + public function down(): void + { + Schema::dropIfExists('summaries'); + } +}; diff --git a/domain/src/Actions/UpdateSummary.php b/domain/src/Actions/UpdateSummary.php new file mode 100644 index 0000000..1f6b15b --- /dev/null +++ b/domain/src/Actions/UpdateSummary.php @@ -0,0 +1,25 @@ + $this->fiatBalanceUpdate->currency, + 'fiat_balance' => new FiatAmount('0', $this->fiatBalanceUpdate->currency), + ]); + + $summary->fill(['fiat_balance' => $summary->fiat_balance->plus($this->fiatBalanceUpdate)])->save(); + } +} diff --git a/domain/src/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAsset.php b/domain/src/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAsset.php index d5d0b35..91c1821 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAsset.php +++ b/domain/src/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAsset.php @@ -19,6 +19,7 @@ public function __construct( public Asset $asset, public LocalDate $date, public FiatAmount $costBasis, + public bool $forFiat, ) { } @@ -32,6 +33,7 @@ public function __invoke(NonFungibleAssetRepository $nonFungibleAssetRepository, asset: $this->asset, date: $this->date, costBasisIncrease: $this->costBasis, + forFiat: $this->forFiat, )); return; @@ -48,6 +50,13 @@ public function getAsset(): Asset public function __toString(): string { - return sprintf('%s (asset: %s, date: %s, cost basis: %s)', self::class, $this->asset, $this->date, $this->costBasis); + return sprintf( + '%s (asset: %s, date: %s, cost basis: %s, for fiat: %s)', + self::class, + $this->asset, + $this->date, + $this->costBasis, + $this->forFiat ? 'yes' : 'no', + ); } } diff --git a/domain/src/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAsset.php b/domain/src/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAsset.php index 20837de..2391b11 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAsset.php +++ b/domain/src/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAsset.php @@ -19,6 +19,7 @@ public function __construct( public Asset $asset, public LocalDate $date, public FiatAmount $proceeds, + public bool $forFiat, ) { } @@ -43,6 +44,13 @@ public function getDate(): LocalDate public function __toString(): string { - return sprintf('%s (asset: %s, date: %s, proceeds: %s)', self::class, $this->asset, $this->date, $this->proceeds); + return sprintf( + '%s (asset: %s, date: %s, proceeds: %s, for fiat: %s)', + self::class, + $this->asset, + $this->date, + $this->proceeds, + $this->forFiat ? 'yes' : 'no', + ); } } diff --git a/domain/src/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasis.php b/domain/src/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasis.php index 5c95f8c..a9fb048 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasis.php +++ b/domain/src/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasis.php @@ -19,6 +19,7 @@ public function __construct( public Asset $asset, public LocalDate $date, public FiatAmount $costBasisIncrease, + public bool $forFiat, ) { } @@ -44,11 +45,12 @@ public function getDate(): LocalDate public function __toString(): string { return sprintf( - '%s (asset: %s, date: %s, cost basis increase: %s)', + '%s (asset: %s, date: %s, cost basis increase: %s, for fiat: %s)', self::class, $this->asset, $this->date, $this->costBasisIncrease, + $this->forFiat ? 'yes' : 'no', ); } } diff --git a/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetAcquired.php b/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetAcquired.php index eedf6c3..cbe5de7 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetAcquired.php +++ b/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetAcquired.php @@ -12,6 +12,7 @@ public function __construct( public LocalDate $date, public FiatAmount $costBasis, + public bool $forFiat, ) { } } diff --git a/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetCostBasisIncreased.php b/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetCostBasisIncreased.php index 96d04d9..d2e6d39 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetCostBasisIncreased.php +++ b/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetCostBasisIncreased.php @@ -13,6 +13,7 @@ public function __construct( public LocalDate $date, public FiatAmount $costBasisIncrease, public FiatAmount $newCostBasis, + public bool $forFiat, ) { } } diff --git a/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetDisposedOf.php b/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetDisposedOf.php index 5366d0a..bfb682c 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetDisposedOf.php +++ b/domain/src/Aggregates/NonFungibleAsset/Events/NonFungibleAssetDisposedOf.php @@ -13,6 +13,7 @@ public function __construct( public LocalDate $date, public FiatAmount $costBasis, public FiatAmount $proceeds, + public bool $forFiat, ) { } } diff --git a/domain/src/Aggregates/NonFungibleAsset/NonFungibleAsset.php b/domain/src/Aggregates/NonFungibleAsset/NonFungibleAsset.php index afaffe6..abcb028 100644 --- a/domain/src/Aggregates/NonFungibleAsset/NonFungibleAsset.php +++ b/domain/src/Aggregates/NonFungibleAsset/NonFungibleAsset.php @@ -53,6 +53,7 @@ public function acquire(AcquireNonFungibleAsset $action): void $this->recordThat(new NonFungibleAssetAcquired( date: $action->date, costBasis: $action->costBasis, + forFiat: $action->forFiat, )); } @@ -76,6 +77,7 @@ public function increaseCostBasis(IncreaseNonFungibleAssetCostBasis $action): vo date: $action->date, costBasisIncrease: $action->costBasisIncrease, newCostBasis: $this->costBasis?->plus($action->costBasisIncrease) ?? $action->costBasisIncrease, + forFiat: $action->forFiat, )); } @@ -101,6 +103,7 @@ public function disposeOf(DisposeOfNonFungibleAsset $action): void date: $action->date, costBasis: $this->costBasis, proceeds: $action->proceeds, + forFiat: $action->forFiat, )); } diff --git a/domain/src/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactor.php b/domain/src/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactor.php index 9753ce8..c8e3362 100644 --- a/domain/src/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactor.php +++ b/domain/src/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactor.php @@ -4,6 +4,9 @@ namespace Domain\Aggregates\NonFungibleAsset\Reactors; +use Domain\Actions\UpdateSummary; +use Domain\Aggregates\NonFungibleAsset\Events\NonFungibleAssetAcquired; +use Domain\Aggregates\NonFungibleAsset\Events\NonFungibleAssetCostBasisIncreased; use Domain\Aggregates\NonFungibleAsset\Events\NonFungibleAssetDisposedOf; use Domain\Aggregates\TaxYear\Actions\UpdateCapitalGain; use Domain\Aggregates\TaxYear\ValueObjects\CapitalGain; @@ -17,11 +20,33 @@ public function __construct(private readonly ActionRunner $runner) { } + public function handleNonFungibleAssetAcquired(NonFungibleAssetAcquired $event, Message $message): void + { + if ($event->forFiat) { + $this->runner->run( + new UpdateSummary(fiatBalanceUpdate: $event->costBasis->minus($event->costBasis->multipliedBy('2'))), + ); + } + } + + public function handleNonFungibleAssetCostBasisIncreased(NonFungibleAssetCostBasisIncreased $event, Message $message): void + { + if ($event->forFiat) { + $this->runner->run( + new UpdateSummary(fiatBalanceUpdate: $event->costBasisIncrease->minus($event->costBasisIncrease->multipliedBy('2'))), + ); + } + } + public function handleNonFungibleAssetDisposedOf(NonFungibleAssetDisposedOf $event, Message $message): void { $this->runner->run(new UpdateCapitalGain( date: $event->date, capitalGainUpdate: new CapitalGain($event->costBasis, $event->proceeds), )); + + if ($event->forFiat) { + $this->runner->run(new UpdateSummary(fiatBalanceUpdate: $event->proceeds)); + } } } diff --git a/domain/src/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAsset.php b/domain/src/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAsset.php index ca7b223..a210ff0 100644 --- a/domain/src/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAsset.php +++ b/domain/src/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAsset.php @@ -22,6 +22,7 @@ public function __construct( public LocalDate $date, public Quantity $quantity, public FiatAmount $costBasis, + public bool $forFiat, // Testing purposes only public ?SharePoolingAssetTransactionId $transactionId = null, ) { diff --git a/domain/src/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAsset.php b/domain/src/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAsset.php index 3e6a207..08d7846 100644 --- a/domain/src/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAsset.php +++ b/domain/src/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAsset.php @@ -22,6 +22,7 @@ public function __construct( public LocalDate $date, public Quantity $quantity, public FiatAmount $proceeds, + public bool $forFiat, // Only present whenever the disposal has been reverted and is now being replayed public ?SharePoolingAssetTransactionId $transactionId = null, ) { diff --git a/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisition.php b/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisition.php index 279b475..49d46e3 100644 --- a/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisition.php +++ b/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisition.php @@ -21,6 +21,7 @@ public function __construct( LocalDate $date, Quantity $quantity, FiatAmount $costBasis, + public readonly bool $forFiat, ?SharePoolingAssetTransactionId $id = null, ?Quantity $sameDayQuantity = null, ?Quantity $thirtyDayQuantity = null, @@ -110,6 +111,12 @@ public function decreaseThirtyDayQuantity(Quantity $quantity): self public function __toString(): string { - return sprintf('%s: acquired %s tokens for %s', $this->date, $this->quantity, $this->costBasis); + return sprintf( + '%s: acquired %s tokens for %s (for fiat: %s)', + $this->date, + $this->quantity, + $this->costBasis, + $this->forFiat ? 'yes' : 'no', + ); } } diff --git a/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposal.php b/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposal.php index 3b6e2c5..e45138b 100644 --- a/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposal.php +++ b/domain/src/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposal.php @@ -23,6 +23,7 @@ public function __construct( Quantity $quantity, FiatAmount $costBasis, public readonly FiatAmount $proceeds, + public readonly bool $forFiat, ?QuantityAllocation $sameDayQuantityAllocation = null, ?QuantityAllocation $thirtyDayQuantityAllocation = null, bool $processed = true, @@ -57,6 +58,7 @@ public function copyAsUnprocessed(): SharePoolingAssetDisposal quantity: $this->quantity, costBasis: $this->costBasis->zero(), proceeds: $this->proceeds, + forFiat: $this->forFiat, processed: false, ); } @@ -84,11 +86,12 @@ public function thirtyDayQuantityAllocatedTo(SharePoolingAssetAcquisition $acqui public function __toString(): string { return sprintf( - '%s: disposed of %s tokens for %s (cost basis: %s)', + '%s: disposed of %s tokens for %s (cost basis: %s, for fiat: %s)', $this->date, $this->quantity, $this->proceeds, $this->costBasis, + $this->forFiat ? 'yes' : 'no', ); } } diff --git a/domain/src/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactor.php b/domain/src/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactor.php index e29c875..7be7a96 100644 --- a/domain/src/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactor.php +++ b/domain/src/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactor.php @@ -4,6 +4,8 @@ namespace Domain\Aggregates\SharePoolingAsset\Reactors; +use Domain\Actions\UpdateSummary; +use Domain\Aggregates\SharePoolingAsset\Events\SharePoolingAssetAcquired; use Domain\Aggregates\SharePoolingAsset\Events\SharePoolingAssetDisposalReverted; use Domain\Aggregates\SharePoolingAsset\Events\SharePoolingAssetDisposedOf; use Domain\Aggregates\TaxYear\Actions\RevertCapitalGainUpdate; @@ -19,12 +21,25 @@ public function __construct(private readonly ActionRunner $runner) { } + public function handleSharePoolingAssetAcquired(SharePoolingAssetAcquired $event, Message $message): void + { + if ($event->acquisition->forFiat) { + $this->runner->run(new UpdateSummary( + fiatBalanceUpdate: $event->acquisition->costBasis->minus($event->acquisition->costBasis->multipliedBy('2')), + )); + } + } + public function handleSharePoolingAssetDisposedOf(SharePoolingAssetDisposedOf $event, Message $message): void { $this->runner->run(new UpdateCapitalGain( date: $event->disposal->date, capitalGainUpdate: new CapitalGain($event->disposal->costBasis, $event->disposal->proceeds), )); + + if ($event->disposal->forFiat) { + $this->runner->run(new UpdateSummary(fiatBalanceUpdate: $event->disposal->proceeds)); + } } public function handleSharePoolingAssetDisposalReverted(SharePoolingAssetDisposalReverted $event, Message $message): void @@ -33,5 +48,11 @@ public function handleSharePoolingAssetDisposalReverted(SharePoolingAssetDisposa date: $event->disposal->date, capitalGainUpdate: new CapitalGain($event->disposal->costBasis, $event->disposal->proceeds), )); + + if ($event->disposal->forFiat) { + $this->runner->run(new UpdateSummary( + fiatBalanceUpdate: $event->disposal->proceeds->minus($event->disposal->proceeds->multipliedBy('2')), + )); + } } } diff --git a/domain/src/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessor.php b/domain/src/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessor.php index 710d9d3..3bdc346 100644 --- a/domain/src/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessor.php +++ b/domain/src/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessor.php @@ -40,6 +40,7 @@ public static function process( quantity: $disposal->quantity, costBasis: $costBasis, proceeds: $disposal->proceeds, + forFiat: $disposal->forFiat, sameDayQuantityAllocation: $sameDayQuantityAllocation, thirtyDayQuantityAllocation: $thirtyDayQuantityAllocation, ); diff --git a/domain/src/Aggregates/SharePoolingAsset/SharePoolingAsset.php b/domain/src/Aggregates/SharePoolingAsset/SharePoolingAsset.php index ab18870..183f7ec 100644 --- a/domain/src/Aggregates/SharePoolingAsset/SharePoolingAsset.php +++ b/domain/src/Aggregates/SharePoolingAsset/SharePoolingAsset.php @@ -79,6 +79,7 @@ public function acquire(AcquireSharePoolingAsset $action): void date: $action->date, quantity: $action->quantity, costBasis: $action->costBasis, + forFiat: $action->forFiat, ), )); @@ -124,6 +125,7 @@ public function disposeOf(DisposeOfSharePoolingAsset $action): void quantity: $action->quantity, costBasis: $action->proceeds->zero(), proceeds: $action->proceeds, + forFiat: $action->forFiat, processed: false, )); @@ -141,6 +143,7 @@ private function replayDisposals(SharePoolingAssetDisposals $disposals): void date: $disposal->date, quantity: $disposal->quantity, proceeds: $disposal->proceeds, + forFiat: $disposal->forFiat, )); } } diff --git a/domain/src/Projections/Summary.php b/domain/src/Projections/Summary.php new file mode 100644 index 0000000..5b28b40 --- /dev/null +++ b/domain/src/Projections/Summary.php @@ -0,0 +1,42 @@ + FiatCurrency::from($value), + set: fn (FiatCurrency $value) => $value->value, + ); + } + + protected function fiatBalance(): Attribute + { + return Attribute::make( + get: fn (?string $value) => new FiatAmount($value ?? '0', $this->currency), + set: fn (FiatAmount $value) => (string) $value->quantity, + ); + } +} diff --git a/domain/src/Repositories/SummaryRepository.php b/domain/src/Repositories/SummaryRepository.php new file mode 100644 index 0000000..ffb8ff9 --- /dev/null +++ b/domain/src/Repositories/SummaryRepository.php @@ -0,0 +1,12 @@ +runner->run(new DisposeOfNonFungibleAsset( asset: $asset, date: $transaction->date, proceeds: $transaction->marketValue->minus($this->splitFees($transaction)), + forFiat: $transaction instanceof Swap && $transaction->acquiredAsset->isFiat(), )); } - private function handleAcquisition(Acquisition|Disposal|Swap $transaction, Asset $asset): void + private function handleAcquisition(Acquisition|Swap $transaction, Asset $asset): void { $this->runner->run(new AcquireNonFungibleAsset( asset: $asset, date: $transaction->date, costBasis: $transaction->marketValue->plus($this->splitFees($transaction)), + forFiat: $transaction instanceof Swap && $transaction->disposedOfAsset->isFiat(), )); } } diff --git a/domain/src/Services/TransactionDispatcher/Handlers/SharePoolingAssetHandler.php b/domain/src/Services/TransactionDispatcher/Handlers/SharePoolingAssetHandler.php index 246f91f..e2ef9ca 100644 --- a/domain/src/Services/TransactionDispatcher/Handlers/SharePoolingAssetHandler.php +++ b/domain/src/Services/TransactionDispatcher/Handlers/SharePoolingAssetHandler.php @@ -49,23 +49,25 @@ public function handle(Acquisition|Disposal|Swap $transaction): void } } - private function handleDisposal(Acquisition|Disposal|Swap $transaction, Asset $asset, Quantity $quantity): void + private function handleDisposal(Disposal|Swap $transaction, Asset $asset, Quantity $quantity): void { $this->runner->run(new DisposeOfSharePoolingAsset( asset: $asset, date: $transaction->date, quantity: $quantity, proceeds: $transaction->marketValue->minus($this->splitFees($transaction)), + forFiat: $transaction instanceof Swap && $transaction->acquiredAsset->isFiat(), )); } - private function handleAcquisition(Acquisition|Disposal|Swap $transaction, Asset $asset, Quantity $quantity): void + private function handleAcquisition(Acquisition|Swap $transaction, Asset $asset, Quantity $quantity): void { $this->runner->run(new AcquireSharePoolingAsset( asset: $asset, date: $transaction->date, quantity: $quantity, costBasis: $transaction->marketValue->plus($this->splitFees($transaction)), + forFiat: $transaction instanceof Swap && $transaction->disposedOfAsset->isFiat(), )); } } diff --git a/domain/src/ValueObjects/Exceptions/FiatAmountException.php b/domain/src/ValueObjects/Exceptions/FiatAmountException.php index 1250ccb..b8f0d96 100644 --- a/domain/src/ValueObjects/Exceptions/FiatAmountException.php +++ b/domain/src/ValueObjects/Exceptions/FiatAmountException.php @@ -15,6 +15,6 @@ private function __construct(string $message) public static function fiatCurrenciesDoNotMatch(string ...$currencies): self { - return new self(sprintf('The fiat currencies don\'t match. Found %s.', implode(', ', $currencies))); + return new self(sprintf('The fiat currencies don\'t match (%s).', implode(', ', $currencies))); } } diff --git a/domain/tests/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAssetTest.php b/domain/tests/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAssetTest.php index e8fcb23..6081469 100644 --- a/domain/tests/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAssetTest.php +++ b/domain/tests/Aggregates/NonFungibleAsset/Actions/AcquireNonFungibleAssetTest.php @@ -18,6 +18,7 @@ asset: Asset::nonFungible('foo'), date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('1'), + forFiat: false, ); $nonFungibleAssetRepository->shouldReceive('get')->once()->andReturn($nonFungibleAsset); @@ -37,6 +38,7 @@ asset: Asset::nonFungible('foo'), date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('1'), + forFiat: false, ); $nonFungibleAssetRepository->shouldReceive('get')->once()->andReturn($nonFungibleAsset); diff --git a/domain/tests/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAssetTest.php b/domain/tests/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAssetTest.php index bfdea16..193f8f9 100644 --- a/domain/tests/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAssetTest.php +++ b/domain/tests/Aggregates/NonFungibleAsset/Actions/DisposeOfNonFungibleAssetTest.php @@ -15,6 +15,7 @@ asset: Asset::nonFungible('foo'), date: LocalDate::parse('2015-10-21'), proceeds: FiatAmount::GBP('1'), + forFiat: false, ); $nonFungibleAssetRepository->shouldReceive('get')->once()->andReturn($nonFungibleAsset); diff --git a/domain/tests/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasisTest.php b/domain/tests/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasisTest.php index 036199b..c7aef0c 100644 --- a/domain/tests/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasisTest.php +++ b/domain/tests/Aggregates/NonFungibleAsset/Actions/IncreaseNonFungibleAssetCostBasisTest.php @@ -15,6 +15,7 @@ asset: Asset::nonFungible('foo'), date: LocalDate::parse('2015-10-21'), costBasisIncrease: FiatAmount::GBP('1'), + forFiat: false, ); $nonFungibleAssetRepository->shouldReceive('get')->once()->andReturn($nonFungibleAsset); diff --git a/domain/tests/Aggregates/NonFungibleAsset/NonFungibleAssetTest.php b/domain/tests/Aggregates/NonFungibleAsset/NonFungibleAssetTest.php index dca2657..67b427f 100644 --- a/domain/tests/Aggregates/NonFungibleAsset/NonFungibleAssetTest.php +++ b/domain/tests/Aggregates/NonFungibleAsset/NonFungibleAssetTest.php @@ -25,11 +25,13 @@ asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); then(new NonFungibleAssetAcquired( date: $acquireNonFungibleAsset->date, costBasis: $acquireNonFungibleAsset->costBasis, + forFiat: false, )); }); @@ -38,36 +40,48 @@ asset: new Asset('FOO'), date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(NonFungibleAssetException::assetIsFungible($acquireNonFungibleAsset)); }); it('cannot acquire the same non-fungible asset more than once', function () { - given(new NonFungibleAssetAcquired(date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'))); + given(new NonFungibleAssetAcquired( + date: LocalDate::parse('2015-10-21'), + costBasis: FiatAmount::GBP('100'), + forFiat: false, + )); when(new AcquireNonFungibleAsset( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(NonFungibleAssetException::alreadyAcquired($this->aggregateRootId->toAsset())); }); it('can increase the cost basis of a non-fungible asset', function () { - given(new NonFungibleAssetAcquired(date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'))); + given(new NonFungibleAssetAcquired( + date: LocalDate::parse('2015-10-21'), + costBasis: FiatAmount::GBP('100'), + forFiat: false, + )); when($increaseNonFungibleAssetCostBasis = new IncreaseNonFungibleAssetCostBasis( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), costBasisIncrease: FiatAmount::GBP('50'), + forFiat: false, )); then(new NonFungibleAssetCostBasisIncreased( date: $increaseNonFungibleAssetCostBasis->date, costBasisIncrease: $increaseNonFungibleAssetCostBasis->costBasisIncrease, newCostBasis: FiatAmount::GBP('150'), + forFiat: false, )); }); @@ -76,6 +90,7 @@ asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), costBasisIncrease: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(NonFungibleAssetException::cannotIncreaseCostBasisBeforeAcquisition($this->aggregateRootId->toAsset())); @@ -85,12 +100,14 @@ given($nonFungibleAssetAcquired = new NonFungibleAssetAcquired( date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); when($increaseNonFungibleAssetCostBasis = new IncreaseNonFungibleAssetCostBasis( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-20'), costBasisIncrease: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(NonFungibleAssetException::olderThanPreviousTransaction( @@ -100,12 +117,17 @@ }); it('cannot increase the cost basis of a non-fungible asset because the currency is different', function () { - given(new NonFungibleAssetAcquired(date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'))); + given(new NonFungibleAssetAcquired( + date: LocalDate::parse('2015-10-21'), + costBasis: FiatAmount::GBP('100'), + forFiat: false, + )); when($increaseNonFungibleAssetCostBasis = new IncreaseNonFungibleAssetCostBasis( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), costBasisIncrease: new FiatAmount('100', FiatCurrency::EUR), + forFiat: false, )); expectToFail(NonFungibleAssetException::currencyMismatch( @@ -119,18 +141,21 @@ given($nonFungibleAssetAcquired = new NonFungibleAssetAcquired( date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); when($disposeOfNonFungibleAsset = new DisposeOfNonFungibleAsset( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), proceeds: FiatAmount::GBP('150'), + forFiat: false, )); then(new NonFungibleAssetDisposedOf( date: $disposeOfNonFungibleAsset->date, costBasis: $nonFungibleAssetAcquired->costBasis, proceeds: $disposeOfNonFungibleAsset->proceeds, + forFiat: false, )); }); @@ -139,18 +164,24 @@ asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), proceeds: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(NonFungibleAssetException::cannotDisposeOfBeforeAcquisition($this->aggregateRootId->toAsset())); }); it('cannot dispose of a non-fungible asset because the currencies don\'t match', function () { - given(new NonFungibleAssetAcquired(date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'))); + given(new NonFungibleAssetAcquired( + date: LocalDate::parse('2015-10-21'), + costBasis: FiatAmount::GBP('100'), + forFiat: false, + )); when($disposeOfNonFungibleAsset = new DisposeOfNonFungibleAsset( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-21'), proceeds: new FiatAmount('100', FiatCurrency::EUR), + forFiat: false, )); expectToFail(NonFungibleAssetException::currencyMismatch( @@ -164,12 +195,14 @@ given($nonFungibleAssetAcquired = new NonFungibleAssetAcquired( date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); when($disposeOfNonFungibleAsset = new DisposeOfNonFungibleAsset( asset: $this->aggregateRootId->toAsset(), date: LocalDate::parse('2015-10-20'), proceeds: FiatAmount::GBP('150'), + forFiat: false, )); expectToFail(NonFungibleAssetException::olderThanPreviousTransaction( diff --git a/domain/tests/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactorTest.php b/domain/tests/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactorTest.php index 7b2cbad..e9b3153 100644 --- a/domain/tests/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactorTest.php +++ b/domain/tests/Aggregates/NonFungibleAsset/Reactors/NonFungibleAssetReactorTest.php @@ -1,6 +1,9 @@ givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) ->when(new Message($nonFungibleAssetDisposedOf)) - ->then(fn () => $this->runner->shouldHaveReceived( - 'run', - fn (UpdateCapitalGain $action) => $action->capitalGainUpdate->difference->isEqualTo('1'), - )->once()); + ->then(function () { + return $this->runner->shouldHaveReceived('run', fn (UpdateCapitalGain $action) => $action->capitalGainUpdate->difference->isEqualTo('1'))->once() + && $this->runner->shouldNotHaveReceived('run', fn ($action) => $action instanceof UpdateSummary); + }); }); it('can handle a capital loss', function () { @@ -31,13 +35,64 @@ date: LocalDate::parse('2015-10-21'), costBasis: FiatAmount::GBP('100'), proceeds: FiatAmount::GBP('99'), + forFiat: false, ); /** @var MessageConsumerTestCase $this */ $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) ->when(new Message($nonFungibleAssetDisposedOf)) + ->then(function () { + return $this->runner->shouldHaveReceived('run', fn (UpdateCapitalGain $action) => $action->capitalGainUpdate->difference->isEqualTo('-1'))->once() + && $this->runner->shouldNotHaveReceived('run', fn ($action) => $action instanceof UpdateSummary); + }); +}); + +it('can handle a summary update for an acquisition', function () { + $nonFungibleAssetAcquired = new NonFungibleAssetAcquired( + date: LocalDate::parse('2015-10-21'), + costBasis: FiatAmount::GBP('10'), + forFiat: true, + ); + + /** @var MessageConsumerTestCase $this */ + $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) + ->when(new Message($nonFungibleAssetAcquired)) + ->then(fn () => $this->runner->shouldHaveReceived( + 'run', + fn ($action) => $action instanceof UpdateSummary && $action->fiatBalanceUpdate->isEqualTo(FiatAmount::GBP('-10')), + )->once()); +}); + +it('can handle a summary update for a cost basis increase', function () { + $nonFungibleAssetCostBasisIncreased = new NonFungibleAssetCostBasisIncreased( + date: LocalDate::parse('2015-10-21'), + costBasisIncrease: FiatAmount::GBP('10'), + newCostBasis: FiatAmount::GBP('20'), + forFiat: true, + ); + + /** @var MessageConsumerTestCase $this */ + $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) + ->when(new Message($nonFungibleAssetCostBasisIncreased)) ->then(fn () => $this->runner->shouldHaveReceived( 'run', - fn (UpdateCapitalGain $action) => $action->capitalGainUpdate->difference->isEqualTo('-1'), + fn ($action) => $action instanceof UpdateSummary && $action->fiatBalanceUpdate->isEqualTo(FiatAmount::GBP('-10')), )->once()); }); + +it('can handle a summary update for a disposal', function () { + $nonFungibleAssetDisposedOf = new NonFungibleAssetDisposedOf( + date: LocalDate::parse('2015-10-21'), + costBasis: FiatAmount::GBP('100'), + proceeds: $proceeds = FiatAmount::GBP('101'), + forFiat: true, + ); + + /** @var MessageConsumerTestCase $this */ + $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) + ->when(new Message($nonFungibleAssetDisposedOf)) + ->then(function () use ($proceeds) { + return $this->runner->shouldHaveReceived('run', fn ($action) => $action instanceof UpdateCapitalGain)->once() + && $this->runner->shouldHaveReceived('run', fn ($action) => $action instanceof UpdateSummary && $action->fiatBalanceUpdate->isEqualTo($proceeds))->once(); + }); +}); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAssetTest.php b/domain/tests/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAssetTest.php index aea96c0..2823ebb 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAssetTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Actions/AcquireSharePoolingAssetTest.php @@ -17,6 +17,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('1'), costBasis: FiatAmount::GBP('1'), + forFiat: false, ); $sharePoolingAssetRepository->shouldReceive('get')->once()->andReturn($sharePoolingAsset); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAssetTest.php b/domain/tests/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAssetTest.php index 5901bf0..734cc56 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAssetTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Actions/DisposeOfSharePoolingAssetTest.php @@ -17,6 +17,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('1'), proceeds: FiatAmount::GBP('1'), + forFiat: false, ); $sharePoolingAssetRepository->shouldReceive('get')->once()->andReturn($sharePoolingAsset); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisitionTest.php b/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisitionTest.php index c45a854..7a6fcc0 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisitionTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetAcquisitionTest.php @@ -24,6 +24,7 @@ date: LocalDate::now(TimeZone::utc()), quantity: Quantity::zero(), costBasis: FiatAmount::GBP('100'), + forFiat: false, ); expect($acquisition->id)->toBeInstanceOf(SharePoolingAssetTransactionId::class); @@ -176,5 +177,5 @@ 'costBasis' => FiatAmount::GBP('100'), ]); - expect((string) $acquisition)->toBe('2015-10-21: acquired 100 tokens for £100.00'); + expect((string) $acquisition)->toBe('2015-10-21: acquired 100 tokens for £100.00 (for fiat: no)'); }); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposalTest.php b/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposalTest.php index 784bd6f..382e52c 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposalTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Entities/SharePoolingAssetDisposalTest.php @@ -38,6 +38,7 @@ quantity: Quantity::zero(), costBasis: FiatAmount::GBP('100'), proceeds: FiatAmount::GBP('150'), + forFiat: false, ); expect($disposal->id)->toBeInstanceOf(SharePoolingAssetTransactionId::class); @@ -129,5 +130,5 @@ 'costBasis' => FiatAmount::GBP('100'), ]); - expect((string) $acquisition)->toBe('2015-10-21: disposed of 100 tokens for £150.00 (cost basis: £100.00)'); + expect((string) $acquisition)->toBe('2015-10-21: disposed of 100 tokens for £150.00 (cost basis: £100.00, for fiat: no)'); }); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetAcquisitionFactory.php b/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetAcquisitionFactory.php index 580b23b..cae3fc8 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetAcquisitionFactory.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetAcquisitionFactory.php @@ -27,6 +27,7 @@ public function definition() 'date' => LocalDate::parse('2015-10-21'), 'quantity' => new Quantity('100'), 'costBasis' => FiatAmount::GBP('100'), + 'forFiat' => false, 'sameDayQuantity' => null, 'thirtyDayQuantity' => null, ]; @@ -38,6 +39,7 @@ public function copyFrom(SharePoolingAssetAcquisition $transaction): static 'date' => $transaction->date, 'quantity' => $transaction->quantity, 'costBasis' => $transaction->costBasis, + 'forFiat' => $transaction->forFiat, ]); } } diff --git a/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetDisposalFactory.php b/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetDisposalFactory.php index 08cac8d..2c33801 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetDisposalFactory.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Factories/Entities/SharePoolingAssetDisposalFactory.php @@ -30,6 +30,7 @@ public function definition() 'quantity' => new Quantity('100'), 'costBasis' => FiatAmount::GBP('100'), 'proceeds' => FiatAmount::GBP('100'), + 'forFiat' => false, 'sameDayQuantityAllocation' => new QuantityAllocation(), 'thirtyDayQuantityAllocation' => new QuantityAllocation(), 'processed' => true, @@ -44,6 +45,7 @@ public function copyFrom(SharePoolingAssetDisposal $transaction): static 'quantity' => $transaction->quantity, 'costBasis' => $transaction->costBasis, 'proceeds' => $transaction->proceeds, + 'forFiat' => $transaction->forFiat, 'sameDayQuantityAllocation' => $transaction->sameDayQuantityAllocation->copy(), 'thirtyDayQuantityAllocation' => $transaction->thirtyDayQuantityAllocation->copy(), ]); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactorTest.php b/domain/tests/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactorTest.php index a6f78e3..710893e 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactorTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Reactors/SharePoolingAssetReactorTest.php @@ -1,6 +1,9 @@ new Quantity('100'), 'costBasis' => FiatAmount::GBP($costBasis), 'proceeds' => FiatAmount::GBP($proceeds), + 'forFiat' => false, ]), ); /** @var MessageConsumerTestCase $this */ $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) ->when(new Message($sharePoolingAssetDisposedOf)) - ->then(fn () => $this->runner->shouldHaveReceived( - 'run', - fn (UpdateCapitalGain $action) => $action->capitalGainUpdate->difference->isEqualTo($capitalGain) - )->once()); + ->then(function () use ($capitalGain) { + return $this->runner->shouldHaveReceived('run', fn (UpdateCapitalGain $action) => $action->capitalGainUpdate->difference->isEqualTo($capitalGain))->once() + && $this->runner->shouldNotHaveReceived('run', fn ($action) => $action instanceof UpdateSummary); + }); })->with([ 'gain' => ['100', '101', '1'], 'loss' => ['100', '99', '-1'], @@ -40,17 +44,69 @@ 'quantity' => new Quantity('100'), 'costBasis' => FiatAmount::GBP($costBasis), 'proceeds' => FiatAmount::GBP($proceeds), + 'forFiat' => false, ]), ); /** @var MessageConsumerTestCase $this */ $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) ->when(new Message($sharePoolingAssetDisposalReverted)) - ->then(fn () => $this->runner->shouldHaveReceived( - 'run', - fn (RevertCapitalGainUpdate $action) => $action->capitalGainUpdate->difference->isEqualTo($capitalGain) - )->once()); + ->then(function () use ($capitalGain) { + return $this->runner->shouldHaveReceived('run', fn (RevertCapitalGainUpdate $action) => $action->capitalGainUpdate->difference->isEqualTo($capitalGain))->once() + && $this->runner->shouldNotHaveReceived('run', fn ($action) => $action instanceof UpdateSummary); + }); })->with([ 'gain' => ['100', '101', '1'], 'loss' => ['100', '99', '-1'], ]); + +it('can handle a summary update for an acquisition', function () { + $sharePoolingAssetAcquired = new SharePoolingAssetAcquired( + SharePoolingAssetAcquisition::factory()->make([ + 'costBasis' => FiatAmount::GBP('10'), + 'forFiat' => true, + ]), + ); + + /** @var MessageConsumerTestCase $this */ + $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) + ->when(new Message($sharePoolingAssetAcquired)) + ->then(fn () => $this->runner->shouldHaveReceived( + 'run', + fn ($action) => $action instanceof UpdateSummary && $action->fiatBalanceUpdate->isEqualTo(FiatAmount::GBP('-10')), + )->once()); +}); + +it('can handle a summary update for a disposal', function () { + $sharePoolingAssetDisposedOf = new SharePoolingAssetDisposedOf( + SharePoolingAssetDisposal::factory()->make([ + 'proceeds' => $proceeds = FiatAmount::GBP('10'), + 'forFiat' => true, + ]), + ); + + /** @var MessageConsumerTestCase $this */ + $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) + ->when(new Message($sharePoolingAssetDisposedOf)) + ->then(function () use ($proceeds) { + return $this->runner->shouldHaveReceived('run', fn ($action) => $action instanceof UpdateCapitalGain)->once() + && $this->runner->shouldHaveReceived('run', fn ($action) => $action instanceof UpdateSummary && $action->fiatBalanceUpdate->isEqualTo($proceeds))->once(); + }); +}); + +it('can handle a summary update for a disposal reversion', function () { + $sharePoolingAssetDisposalReverted = new SharePoolingAssetDisposalReverted( + SharePoolingAssetDisposal::factory()->make([ + 'proceeds' => FiatAmount::GBP('10'), + 'forFiat' => true, + ]), + ); + + /** @var MessageConsumerTestCase $this */ + $this->givenNextMessagesHaveAggregateRootIdOf($this->aggregateRootId) + ->when(new Message($sharePoolingAssetDisposalReverted)) + ->then(function () { + return $this->runner->shouldHaveReceived('run', fn ($action) => $action instanceof RevertCapitalGainUpdate)->once() + && $this->runner->shouldHaveReceived('run', fn ($action) => $action instanceof UpdateSummary && $action->fiatBalanceUpdate->isEqualTo(FiatAmount::GBP('-10')))->once(); + }); +}); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessorTest.php b/domain/tests/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessorTest.php index db6fee4..483e1c5 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessorTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Services/DisposalProcessor/DisposalProcessorTest.php @@ -17,6 +17,7 @@ date: LocalDate::parse('2015-10-21'), quantity: Quantity::zero(), proceeds: FiatAmount::GBP('0'), + forFiat: false, ); $disposal = DisposalProcessor::process( @@ -34,6 +35,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), proceeds: FiatAmount::GBP('0'), + forFiat: false, ); $acquisition = SharePoolingAssetAcquisition::factory()->make(['date' => LocalDate::parse('2021-10-22')]); diff --git a/domain/tests/Aggregates/SharePoolingAsset/Services/ReversionFinder/ReversionFinderTest.php b/domain/tests/Aggregates/SharePoolingAsset/Services/ReversionFinder/ReversionFinderTest.php index 946231d..49ef891 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/Services/ReversionFinder/ReversionFinderTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/Services/ReversionFinder/ReversionFinderTest.php @@ -14,6 +14,7 @@ date: LocalDate::parse('2015-10-21'), quantity: Quantity::zero(), costBasis: FiatAmount::GBP('0'), + forFiat: false, ); $disposals = ReversionFinder::disposalsToRevertOnAcquisition($acquisition, SharePoolingAssetTransactions::make()); diff --git a/domain/tests/Aggregates/SharePoolingAsset/SharePoolingAssetTest.php b/domain/tests/Aggregates/SharePoolingAsset/SharePoolingAssetTest.php index 7a836ce..771455d 100644 --- a/domain/tests/Aggregates/SharePoolingAsset/SharePoolingAssetTest.php +++ b/domain/tests/Aggregates/SharePoolingAsset/SharePoolingAssetTest.php @@ -31,6 +31,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); then( @@ -41,6 +42,7 @@ date: $acquireSharePoolingAsset->date, quantity: new Quantity('100'), costBasis: $acquireSharePoolingAsset->costBasis, + forFiat: false, ), ), ); @@ -54,6 +56,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -63,6 +66,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('300'), + forFiat: false, )); then(new SharePoolingAssetAcquired( @@ -71,6 +75,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('300'), + forFiat: false, ), )); }); @@ -83,6 +88,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -91,6 +97,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: new FiatAmount('300', FiatCurrency::EUR), + forFiat: false, )); expectToFail(SharePoolingAssetException::currencyMismatch( @@ -108,6 +115,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -116,6 +124,7 @@ date: LocalDate::parse('2015-10-20'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(SharePoolingAssetException::olderThanPreviousTransaction( @@ -132,6 +141,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('200'), + forFiat: false, ), )); @@ -141,6 +151,7 @@ date: LocalDate::parse('2015-10-25'), quantity: new Quantity('50'), proceeds: FiatAmount::GBP('150'), + forFiat: false, )); then(new SharePoolingAssetDisposedOf( @@ -150,6 +161,7 @@ quantity: $disposeOfSharePoolingAsset->quantity, costBasis: FiatAmount::GBP('100'), proceeds: $disposeOfSharePoolingAsset->proceeds, + forFiat: false, ), )); }); @@ -162,6 +174,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -170,6 +183,7 @@ date: LocalDate::parse('2015-10-25'), quantity: new Quantity('100'), proceeds: new FiatAmount('100', FiatCurrency::EUR), + forFiat: false, )); expectToFail(SharePoolingAssetException::currencyMismatch( @@ -187,6 +201,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -195,6 +210,7 @@ date: LocalDate::parse('2015-10-20'), quantity: new Quantity('100'), proceeds: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(SharePoolingAssetException::olderThanPreviousTransaction( @@ -211,6 +227,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -219,6 +236,7 @@ date: LocalDate::parse('2015-10-25'), quantity: new Quantity('101'), proceeds: FiatAmount::GBP('100'), + forFiat: false, )); expectToFail(SharePoolingAssetException::insufficientQuantity( @@ -236,6 +254,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -244,6 +263,7 @@ date: LocalDate::parse('2015-10-23'), quantity: new Quantity('50'), costBasis: FiatAmount::GBP('65'), + forFiat: false, ), )); @@ -253,6 +273,7 @@ date: LocalDate::parse('2015-10-25'), quantity: new Quantity('20'), proceeds: FiatAmount::GBP('40'), + forFiat: false, )); then(new SharePoolingAssetDisposedOf( @@ -274,6 +295,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -282,6 +304,7 @@ date: LocalDate::parse('2015-10-26'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('150'), + forFiat: false, ), )); @@ -291,6 +314,7 @@ date: LocalDate::parse('2015-10-26'), quantity: new Quantity('150'), proceeds: FiatAmount::GBP('300'), + forFiat: false, )); then(new SharePoolingAssetDisposedOf( @@ -314,6 +338,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -322,6 +347,7 @@ date: LocalDate::parse('2015-10-23'), quantity: new Quantity('50'), costBasis: FiatAmount::GBP('65'), + forFiat: false, ), )); @@ -331,6 +357,7 @@ quantity: new Quantity('20'), costBasis: FiatAmount::GBP('22'), proceeds: FiatAmount::GBP('40'), + forFiat: false, ), )); @@ -339,6 +366,7 @@ date: LocalDate::parse('2015-10-26'), quantity: new Quantity('30'), costBasis: FiatAmount::GBP('36'), + forFiat: false, ), )); @@ -348,6 +376,7 @@ date: LocalDate::parse('2015-10-26'), quantity: new Quantity('60'), proceeds: FiatAmount::GBP('70'), + forFiat: false, )); then(new SharePoolingAssetDisposedOf( @@ -371,6 +400,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -380,6 +410,7 @@ quantity: new Quantity('50'), costBasis: FiatAmount::GBP('50'), proceeds: FiatAmount::GBP('75'), + forFiat: false, ), )); @@ -389,6 +420,7 @@ date: LocalDate::parse('2015-10-29'), quantity: new Quantity('25'), costBasis: FiatAmount::GBP('20'), + forFiat: false, )); then( @@ -399,6 +431,7 @@ date: $acquireMoreSharePoolingAsset->date, quantity: $acquireMoreSharePoolingAsset->quantity, costBasis: $acquireMoreSharePoolingAsset->costBasis, + forFiat: false, thirtyDayQuantity: new Quantity('25'), ), ), @@ -422,6 +455,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -430,6 +464,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('25'), costBasis: FiatAmount::GBP('50'), + forFiat: false, sameDayQuantity: new Quantity('25'), ), )); @@ -450,6 +485,7 @@ quantity: new Quantity('25'), costBasis: FiatAmount::GBP('25'), proceeds: FiatAmount::GBP('50'), + forFiat: false, ), )); @@ -458,6 +494,7 @@ date: LocalDate::parse('2015-10-28'), quantity: new Quantity('20'), costBasis: FiatAmount::GBP('60'), + forFiat: false, thirtyDayQuantity: new Quantity('20'), ), )); @@ -477,6 +514,7 @@ date: LocalDate::parse('2015-10-29'), quantity: new Quantity('20'), costBasis: FiatAmount::GBP('40'), + forFiat: false, )); then( @@ -488,6 +526,7 @@ date: $acquireSharePoolingAsset4->date, quantity: $acquireSharePoolingAsset4->quantity, costBasis: $acquireSharePoolingAsset4->costBasis, + forFiat: false, thirtyDayQuantity: new Quantity('20'), ), ), @@ -514,6 +553,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -522,6 +562,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('25'), costBasis: FiatAmount::GBP('50'), + forFiat: false, sameDayQuantity: new Quantity('25'), ), )); @@ -550,6 +591,7 @@ date: LocalDate::parse('2015-10-28'), quantity: new Quantity('20'), costBasis: FiatAmount::GBP('60'), + forFiat: false, thirtyDayQuantity: new Quantity('20'), ), )); @@ -568,6 +610,7 @@ date: LocalDate::parse('2015-10-29'), quantity: new Quantity('20'), costBasis: FiatAmount::GBP('40'), + forFiat: false, thirtyDayQuantity: new Quantity('20'), ), )); @@ -598,6 +641,7 @@ date: LocalDate::parse('2015-10-29'), quantity: new Quantity('10'), proceeds: FiatAmount::GBP('30'), + forFiat: false, )); then( @@ -631,6 +675,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -640,6 +685,7 @@ quantity: new Quantity('50'), costBasis: FiatAmount::GBP('50'), proceeds: FiatAmount::GBP('75'), + forFiat: false, ), )); @@ -650,6 +696,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('20'), costBasis: FiatAmount::GBP('25'), + forFiat: false, sameDayQuantity: new Quantity('20'), // $sharePoolingAssetDisposedOf ), )); @@ -671,6 +718,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('10'), costBasis: FiatAmount::GBP('14'), + forFiat: false, )); then( @@ -681,6 +729,7 @@ date: $acquireSharePoolingAsset3->date, quantity: $acquireSharePoolingAsset3->quantity, costBasis: $acquireSharePoolingAsset3->costBasis, + forFiat: false, sameDayQuantity: new Quantity('10'), // $sharePoolingAssetDisposedOf ), ), @@ -702,6 +751,7 @@ date: LocalDate::parse('2015-10-21'), quantity: new Quantity('100'), costBasis: FiatAmount::GBP('100'), + forFiat: false, ), )); @@ -711,6 +761,7 @@ quantity: new Quantity('50'), costBasis: FiatAmount::GBP('50'), proceeds: FiatAmount::GBP('75'), + forFiat: false, ), )); @@ -721,6 +772,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('20'), costBasis: FiatAmount::GBP('25'), + forFiat: false, sameDayQuantity: new Quantity('20'), // $sharePoolingAssetDisposedOf1 ), )); @@ -739,6 +791,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('60'), costBasis: FiatAmount::GBP('90'), + forFiat: false, sameDayQuantity: new Quantity('30'), // $sharePoolingAssetDisposedOf1 ), )); @@ -756,6 +809,7 @@ date: LocalDate::parse('2015-10-22'), quantity: new Quantity('40'), proceeds: FiatAmount::GBP('50'), + forFiat: false, )); then(new SharePoolingAssetDisposedOf( diff --git a/tests/Feature/Actions/UpdateSummaryTest.php b/tests/Feature/Actions/UpdateSummaryTest.php new file mode 100644 index 0000000..55b255d --- /dev/null +++ b/tests/Feature/Actions/UpdateSummaryTest.php @@ -0,0 +1,56 @@ +assertDatabaseCount('summaries', 0); + + $updateSummary = new UpdateSummary($fiatBalance = FiatAmount::GBP($balanceUpdate)); + + $updateSummary(); + + $this->assertDatabaseCount('summaries', 1); + + $this->assertDatabaseHas('summaries', [ + 'currency' => $fiatBalance->currency->value, + 'fiat_balance' => $fiatBalance->quantity, + ]); +})->with([ + 'positive' => '10', + 'negative' => '-10', +]); + +it('can update a summary', function (string $balanceUpdate, string $result) { + Summary::create(['currency' => FiatCurrency::GBP, 'fiat_balance' => FiatAmount::GBP('5')]); + + $this->assertDatabaseCount('summaries', 1); + + $updateSummary = new UpdateSummary($fiatBalance = FiatAmount::GBP($balanceUpdate)); + + $updateSummary(); + + $this->assertDatabaseCount('summaries', 1); + + $this->assertDatabaseHas('summaries', [ + 'currency' => $fiatBalance->currency->value, + 'fiat_balance' => $result, + ]); +})->with([ + 'positive' => ['10', '15'], + 'negative' => ['-10', '-5'], +]); + +it('cannot update a summary because the currencies don\'t match', function () { + Summary::create(['currency' => FiatCurrency::GBP, 'fiat_balance' => FiatAmount::GBP('5')]); + + $updateSummary = new UpdateSummary(new FiatAmount('10', FiatCurrency::EUR)); + + expect(fn () => $updateSummary())->toThrow( + FiatAmountException::class, + FiatAmountException::fiatCurrenciesDoNotMatch(FiatCurrency::GBP->value, FiatCurrency::EUR->value)->getMessage(), + ); +}); diff --git a/tests/Feature/Commands/ProcessTest.php b/tests/Feature/Commands/ProcessTest.php index b69bc9c..fa05052 100644 --- a/tests/Feature/Commands/ProcessTest.php +++ b/tests/Feature/Commands/ProcessTest.php @@ -4,7 +4,7 @@ use Domain\Enums\FiatCurrency; use LaravelZero\Framework\Commands\Command; -it('can process a spreadhseet', function () { +it('can process a spreadsheet', function () { $this->instance(CommandRunnerContract::class, $commandRunner = Mockery::spy(CommandRunnerContract::class)); $path = base_path('tests/stubs/transactions/valid.csv'); @@ -37,5 +37,10 @@ 'non_attributable_allowable_cost' => '117', ]); + $this->assertDatabaseHas('summaries', [ + 'currency' => FiatCurrency::GBP->value, + 'fiat_balance' => '3330', + ]); + $commandRunner->shouldHaveReceived('run')->once()->with('review'); }); diff --git a/tests/Unit/Commands/ReviewTest.php b/tests/Unit/Commands/ReviewTest.php index 4a0659d..8afe2c2 100644 --- a/tests/Unit/Commands/ReviewTest.php +++ b/tests/Unit/Commands/ReviewTest.php @@ -6,14 +6,18 @@ use Domain\Aggregates\TaxYear\ValueObjects\CapitalGain; use Domain\Aggregates\TaxYear\ValueObjects\TaxYearId; use Domain\Enums\FiatCurrency; +use Domain\Projections\Summary; +use Domain\Repositories\SummaryRepository; use Domain\ValueObjects\FiatAmount; use Illuminate\Database\QueryException; use LaravelZero\Framework\Commands\Command; beforeEach(function () { + $this->summaryRepository = Mockery::mock(SummaryRepository::class); $this->taxYearSummaryRepository = Mockery::mock(TaxYearSummaryRepository::class); $this->instance(TaxYearSummaryRepository::class, $this->taxYearSummaryRepository); + $this->instance(SummaryRepository::class, $this->summaryRepository); }); it('cannot review a tax year because the submitted tax year is invalid', function (mixed $value) { @@ -41,6 +45,10 @@ }); it('cannot review a tax year because the submitted tax year is not available', function () { + $this->summaryRepository->shouldReceive('get') + ->once() + ->andReturn(Summary::make(['currency' => FiatCurrency::GBP, 'fiat_balance' => FiatAmount::GBP('10')])); + $this->taxYearSummaryRepository->shouldReceive('all')->andReturn([['tax_year_id' => TaxYearId::fromString('2021-2022')]]); $this->taxYearSummaryRepository->shouldReceive('find')->andReturn(TaxYearSummary::factory()->make()); @@ -48,6 +56,10 @@ }); it('can review a tax year', function () { + $this->summaryRepository->shouldReceive('get') + ->once() + ->andReturn(Summary::make(['currency' => FiatCurrency::GBP, 'fiat_balance' => FiatAmount::GBP('10')])); + $taxYearId = TaxYearId::fromString('2021-2022'); $this->taxYearSummaryRepository->shouldReceive('all')->once()->andReturn([['tax_year_id' => $taxYearId]]); @@ -66,6 +78,7 @@ ])); $presenter = Mockery::mock(PresenterContract::class); + $presenter->shouldReceive('info')->once()->with('Current fiat balance: £10.00')->andReturn(); $presenter->shouldReceive('summary') ->once() ->with($taxYearId->toString(), '£4.00', '£2.00', '£1.00', '£3.00', '£1.00', '£10.00') @@ -77,6 +90,10 @@ }); it('offers to choose a tax year', function () { + $this->summaryRepository->shouldReceive('get') + ->once() + ->andReturn(Summary::make(['currency' => FiatCurrency::GBP, 'fiat_balance' => FiatAmount::GBP('-10')])); + $this->taxYearSummaryRepository->shouldReceive('all')->once()->andReturn([ ['tax_year_id' => TaxYearId::fromString('2021-2022')], ['tax_year_id' => TaxYearId::fromString('2022-2023')], @@ -93,7 +110,8 @@ ->andReturn(TaxYearSummary::factory()->make()); $presenter = Mockery::mock(PresenterContract::class); - $presenter->shouldReceive('choice')->once()->with('Please select a tax year', ['2022-2023', '2021-2022'], '2022-2023')->andReturn('2022-2023'); + $presenter->shouldReceive('info')->once()->with('Current fiat balance: £-10.00')->andReturn(); + $presenter->shouldReceive('choice')->once()->with('Please select a tax year for details', ['2022-2023', '2021-2022'], '2022-2023')->andReturn('2022-2023'); $presenter->shouldReceive('choice')->once()->with('Review another tax year?', ['No', '2022-2023', '2021-2022'], 'No')->andReturn('2021-2022'); $presenter->shouldReceive('choice')->once()->with('Review another tax year?', ['No', '2022-2023', '2021-2022'], 'No')->andReturn('No'); $presenter->shouldReceive('summary')->twice()->andReturn(); diff --git a/tests/stubs/transactions/valid.csv b/tests/stubs/transactions/valid.csv index 254a5f7..abdefc7 100644 --- a/tests/stubs/transactions/valid.csv +++ b/tests/stubs/transactions/valid.csv @@ -8,7 +8,7 @@ Date,Operation,Market value,Sent asset,Sent quantity,Sent asset is non-fungible, 21/10/2020,swap,1980,GBP,2000,FALSE,BTC,0.099,FALSE,GBP,20,20,FALSE 21/10/2020,transfer,,BTC,0.0989,FALSE,,,FALSE,BTC,0.0001,2,FALSE 02/01/2021,transfer,,BTC,0.5,FALSE,,,FALSE,BTC,0.0005,10,FALSE -02/01/2021,swap,10000,BTC,0.5,FALSE,GBP,5940,FALSE,GBP,60,60,FALSE +02/01/2021,swap,10000,BTC,0.5,FALSE,GBP,9940,FALSE,GBP,60,60,FALSE 05/02/2021,swap,500,ETH,0.25,FALSE,1111,1,TRUE,ETH,0.0025,5,FALSE 05/02/2021,swap,500,ETH,0.25,FALSE,2222,1,TRUE,ETH,0.0025,5,FALSE 15/02/2021,swap,500,1111,1,TRUE,3333,1,TRUE,ETH,0.0025,5,FALSE