118 lines
3.9 KiB
PHP
118 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Api\BulkUpdateTransactionRequest;
|
|
use App\Http\Requests\Api\CheckDuplicateTransactionsRequest;
|
|
use App\Models\Transaction;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
|
|
class TransactionController extends Controller
|
|
{
|
|
/**
|
|
* Return paginated transactions for the authenticated user with optional filters.
|
|
*/
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$query = $request->user()
|
|
->transactions()
|
|
->with('labels');
|
|
|
|
if ($request->query('encrypted') === 'true') {
|
|
$query->where(fn ($q) => $q->whereNotNull('description_iv')->orWhereNotNull('notes_iv'));
|
|
}
|
|
|
|
if ($accountId = $request->query('account_id')) {
|
|
$query->where('account_id', $accountId)
|
|
->orderBy('transaction_date', 'desc')
|
|
->orderBy('id', 'desc');
|
|
}
|
|
|
|
$perPage = min(max((int) $request->query('per_page', 100), 1), 100);
|
|
|
|
$transactions = $query->simplePaginate($perPage);
|
|
|
|
return response()->json($transactions);
|
|
}
|
|
|
|
/**
|
|
* Flag which of the given (date, amount, description) tuples already exist
|
|
* on the account. Replaces the old client-side IndexedDB duplicate check.
|
|
*
|
|
* Matching mirrors the previous frontend logic: same day, exact amount (both
|
|
* in integer cents), and case-insensitive description with collapsed
|
|
* whitespace. Returns a boolean per input transaction, in order.
|
|
*/
|
|
public function checkDuplicates(CheckDuplicateTransactionsRequest $request): JsonResponse
|
|
{
|
|
$validated = $request->validated();
|
|
$account = $request->user()->accounts()->findOrFail($validated['account_id']);
|
|
$incoming = $validated['transactions'];
|
|
|
|
$dates = array_map(fn (array $t): string => substr($t['transaction_date'], 0, 10), $incoming);
|
|
|
|
$existing = $account->transactions()
|
|
->whereBetween('transaction_date', [min($dates), max($dates)])
|
|
->get(['transaction_date', 'amount', 'description']);
|
|
|
|
$seen = [];
|
|
foreach ($existing as $transaction) {
|
|
$seen[$this->duplicateKey(
|
|
$transaction->transaction_date->format('Y-m-d'),
|
|
(int) $transaction->amount,
|
|
$transaction->description,
|
|
)] = true;
|
|
}
|
|
|
|
$duplicates = array_map(
|
|
fn (array $t): bool => isset($seen[$this->duplicateKey(
|
|
substr($t['transaction_date'], 0, 10),
|
|
(int) $t['amount'],
|
|
$t['description'],
|
|
)]),
|
|
$incoming,
|
|
);
|
|
|
|
return response()->json(['duplicates' => $duplicates]);
|
|
}
|
|
|
|
private function duplicateKey(string $date, int $amount, string $description): string
|
|
{
|
|
$normalized = preg_replace('/\s+/', ' ', trim(mb_strtolower($description)));
|
|
|
|
return $date.'|'.$amount.'|'.$normalized;
|
|
}
|
|
|
|
/**
|
|
* Bulk update transactions (used for decryption migration).
|
|
*/
|
|
public function bulkUpdate(BulkUpdateTransactionRequest $request): JsonResponse
|
|
{
|
|
$validated = $request->validated();
|
|
|
|
$transactionIds = collect($validated['transactions'])->pluck('id');
|
|
|
|
$userTransactionIds = $request->user()
|
|
->transactions()
|
|
->whereIn('id', $transactionIds)
|
|
->pluck('id');
|
|
|
|
if ($userTransactionIds->count() !== $transactionIds->count()) {
|
|
abort(403, 'Some transactions do not belong to the authenticated user.');
|
|
}
|
|
|
|
foreach ($validated['transactions'] as $data) {
|
|
$updateData = collect($data)->except('id')->toArray();
|
|
|
|
Transaction::query()
|
|
->where('id', $data['id'])
|
|
->toBase()
|
|
->update($updateData);
|
|
}
|
|
|
|
return response()->json(['message' => 'Transactions updated successfully.']);
|
|
}
|
|
}
|