Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion app/Http/Controllers/API/v1/TransactionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ public function store(Request $request): JsonResponse
$transaction->categories()->sync($categories);
}

if (isset($data['group_id'])) {
$transaction->groups()->sync($data['group_id']);
}

if ($request->hasFile('files')) {
foreach ($request->file('files') as $file) {
$path = $file->store('transactions');
Expand Down Expand Up @@ -417,13 +421,14 @@ public function show($id, Request $request): JsonResponse
new OA\Property(property: 'type', type: 'string', enum: ['income', 'expense']),
new OA\Property(property: 'date', type: 'string', format: 'date'),
new OA\Property(property: 'party_id', type: 'integer'),
new OA\Property(property: 'group_id', type: 'integer'),
new OA\Property(property: 'description', type: 'string'),
new OA\Property(property: 'wallet_id', type: 'integer'),
new OA\Property(property: 'group_id', type: 'integer'),
new OA\Property(property: 'is_recurring', description: 'Set the transaction as a recurring transaction', type: 'boolean'),
new OA\Property(property: 'recurrence_period', description: 'Set how often the transaction should repeat', type: 'string'),
new OA\Property(property: 'recurrence_interval', description: 'Set how often the transaction should repeat', type: 'integer'),
new OA\Property(property: 'recurrence_ends_at', description: 'When the transaction stops repeating', type: 'date-time'),
new OA\Property(property: 'categories', type: 'array', items: new OA\Items(description: 'Category ID array', type: 'integer')),
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time'),
]
)
Expand Down Expand Up @@ -541,6 +546,10 @@ public function update(Request $request, $id): JsonResponse
$transaction->categories()->sync($validatedData['categories']);
}

if (isset($validatedData['group_id'])) {
$transaction->groups()->sync([$validatedData['group_id']]);
}

$user = $request->user();
if (isset($request['client_id']) && ! $transaction->client_id) {
$transaction->setClientGeneratedId($request['client_id'], $user);
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Traits/ApiQueryable.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected function applyApiQuery(Request $request, Builder|Relation $query): arr
if ($request->has('synced_since')) {
try {
$date = Carbon::parse($request->synced_since);
$query = $query->where('updated_at', '>=', $date)->withTrashed();
$query = $query->where('updated_at', '>', $date)->withTrashed();
} catch (\Exception $exception) {
throw new \InvalidArgumentException('Invalid date format for synced_since parameter.');
}
Expand Down
4 changes: 3 additions & 1 deletion app/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Models;

use App\Traits\Groupable;
use App\Traits\HasClientCreatedAt;
use App\Traits\Syncable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand Down Expand Up @@ -47,7 +48,7 @@
)]
class Transaction extends Model
{
use HasClientCreatedAt, HasFactory, SoftDeletes, Syncable;
use Groupable, HasClientCreatedAt, HasFactory, SoftDeletes, Syncable;

/**
* The attributes that are mass assignable.
Expand All @@ -70,6 +71,7 @@ class Transaction extends Model
protected $appends = [
'wallet',
'party',
'group',
'categories',
'last_synced_at',
'client_generated_id',
Expand Down
24 changes: 24 additions & 0 deletions app/Traits/Groupable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Traits;

use App\Models\Group;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

trait Groupable
{
public function getGroupsAttribute()
{
return $this->groups()->get();
}

public function getGroupAttribute()
{
return $this->groups()->first();
}

public function groups(): MorphToMany
{
return $this->morphToMany(Group::class, 'groupable');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public function up(): void
{
Schema::create('groupables', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('group_id');
$table->unsignedBigInteger('groupable_id');
$table->string('groupable_type');
$table->timestamps();
Expand Down
13 changes: 10 additions & 3 deletions public/docs/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -1359,15 +1359,15 @@
"party_id": {
"type": "integer"
},
"group_id": {
"type": "integer"
},
"description": {
"type": "string"
},
"wallet_id": {
"type": "integer"
},
"group_id": {
"type": "integer"
},
"is_recurring": {
"description": "Set the transaction as a recurring transaction",
"type": "boolean"
Expand All @@ -1384,6 +1384,13 @@
"description": "When the transaction stops repeating",
"type": "date-time"
},
"categories": {
"type": "array",
"items": {
"description": "Category ID array",
"type": "integer"
}
},
"updated_at": {
"type": "string",
"format": "date-time"
Expand Down
39 changes: 29 additions & 10 deletions tests/Feature/TransactionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class TransactionsTest extends TestCase

private $party;

private $group;

private $category;

private $user;

public function test_api_user_can_create_transactions()
Expand All @@ -37,6 +41,8 @@ private function createTransaction(string $type, array $recurrentData = []): arr
'description' => 'Test transaction description',
'wallet_id' => $this->wallet->id,
'party_id' => $this->party->id,
'group_id' => $this->group->id,
'categories' => [$this->category->id],
'datetime' => '2025-04-30T15:17:54.120Z',
];

Expand All @@ -58,6 +64,8 @@ private function createTransaction(string $type, array $recurrentData = []): arr
'description',
'datetime',
'party_id',
'group',
'categories',
'wallet_id',
'user_id',
'client_generated_id',
Expand Down Expand Up @@ -440,6 +448,16 @@ public function test_next_scheduled_transaction_should_not_run_if_recurrence_cha
$this->assertEquals(2, $transactions);
}

private function runQueueWorkerOnce(): void
{
Artisan::call('queue:work', [
'connection' => 'database',
'--once' => true,
'--sleep' => 0,
'--tries' => 1,
]);
}

public function test_recurring_transaction_runs_on_next_scheduled_date()
{
// Test weekly recurrence
Expand All @@ -460,16 +478,6 @@ public function test_recurring_transaction_runs_on_next_scheduled_date()
$this->assertEquals(2, $transactions);
}

private function runQueueWorkerOnce(): void
{
Artisan::call('queue:work', [
'connection' => 'database',
'--once' => true,
'--sleep' => 0,
'--tries' => 1,
]);
}

public function test_api_user_can_create_recurring_transactions_with_different_periods()
{
// Test weekly recurrence
Expand Down Expand Up @@ -811,6 +819,17 @@ protected function setUp(): void
'name' => 'Party',
'type' => 'personal',
]);

$this->group = User::factory()->create()->groups()->create([
'name' => 'Group',
'type' => 'personal',
]);

$this->category = User::factory()->create()->categories()->create([
'name' => 'Category',
'type' => 'income',
]);

$this->user = User::factory()->create();
}
}