add payment option

This commit is contained in:
2025-10-02 18:35:02 +02:00
parent 0e0912c81b
commit 971a9e89d1
27 changed files with 1327 additions and 34 deletions
@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreBookingRequest;
use App\Models\Account;
use App\Models\Booking;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class AccountBookingController extends Controller
{
public function index(Account $account): Response
{
$bookings = Booking::query()
->where('account_id', $account->id)
->orderByDesc('booked_at')
->get(['id', 'payment_id', 'amount_cents', 'type', 'description', 'booked_at', 'created_at']);
return Inertia::render('Accounts/Bookings/Index', [
'account' => $account->only(['id', 'reference', 'description']),
'bookings' => $bookings,
]);
}
public function store(StoreBookingRequest $request, Account $account): RedirectResponse
{
$validated = $request->validated();
Booking::query()->create([
'account_id' => $account->id,
'payment_id' => $validated['payment_id'] ?? null,
'amount_cents' => (int) round(((float) $validated['amount']) * 100),
'type' => $validated['type'],
'description' => $validated['description'] ?? null,
'booked_at' => $validated['booked_at'] ?? now(),
]);
return back()->with('success', 'Booking created.');
}
public function destroy(Account $account, Booking $booking): RedirectResponse
{
if ($booking->account_id !== $account->id) {
abort(404);
}
$booking->delete();
return back()->with('success', 'Booking deleted.');
}
}
@@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePaymentRequest;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Booking;
use App\Models\Payment;
use App\Models\PaymentSetting;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Inertia\Inertia;
use Inertia\Response;
class AccountPaymentController extends Controller
{
public function index(Account $account): Response
{
$payments = Payment::query()
->where('account_id', $account->id)
->orderByDesc('paid_at')
->get(['id', 'amount_cents', 'currency', 'reference', 'paid_at', 'created_at'])
->map(function (Payment $p) {
return [
'id' => $p->id,
'amount' => $p->amount, // accessor divides cents
'currency' => $p->currency,
'reference' => $p->reference,
'paid_at' => $p->paid_at,
'created_at' => $p->created_at,
];
});
return Inertia::render('Accounts/Payments/Index', [
'account' => $account->only(['id', 'reference', 'description']),
'payments' => $payments,
]);
}
public function list(Account $account): JsonResponse
{
$payments = Payment::query()
->where('account_id', $account->id)
->orderByDesc('paid_at')
->get(['id', 'amount_cents', 'currency', 'reference', 'paid_at', 'created_at'])
->map(function (Payment $p) {
return [
'id' => $p->id,
'amount' => $p->amount,
'currency' => $p->currency,
'reference' => $p->reference,
'paid_at' => optional($p->paid_at)?->toDateString(),
'created_at' => optional($p->created_at)?->toDateTimeString(),
];
});
return response()->json([
'account' => [
'id' => $account->id,
'balance_amount' => $account->balance_amount,
],
'payments' => $payments,
]);
}
public function store(StorePaymentRequest $request, Account $account): RedirectResponse
{
$validated = $request->validated();
$amountCents = (int) round(((float) $validated['amount']) * 100);
// Load defaults from settings
$settings = PaymentSetting::query()->first();
$defaultCurrency = strtoupper($settings->default_currency ?? 'EUR');
$payment = Payment::query()->create([
'account_id' => $account->id,
'amount_cents' => $amountCents,
'currency' => strtoupper($validated['currency'] ?? $defaultCurrency),
'reference' => $validated['reference'] ?? null,
'paid_at' => $validated['paid_at'] ?? now(),
'meta' => $validated['meta'] ?? null,
'created_by' => $request->user()?->id,
]);
// Auto-create a credit booking for this payment to reduce account balance
Booking::query()->create([
'account_id' => $account->id,
'payment_id' => $payment->id,
'amount_cents' => $amountCents,
'type' => 'credit',
'description' => $payment->reference ? ('Plačilo '.$payment->reference) : 'Plačilo',
'booked_at' => $payment->paid_at ?? now(),
]);
// Optionally create an activity entry with default decision/action
if ($settings && ($settings->create_activity_on_payment ?? false)) {
$note = $settings->activity_note_template ?? 'Prejeto plačilo';
$note = str_replace(['{amount}', '{currency}'], [number_format($amountCents / 100, 2, ',', '.'), $payment->currency], $note);
$account->loadMissing('contract');
$clientCaseId = $account->contract?->client_case_id;
if ($clientCaseId) {
$activity = Activity::query()->create([
'due_date' => null,
'amount' => $amountCents / 100,
'note' => $note,
'action_id' => $settings->default_action_id,
'decision_id' => $settings->default_decision_id,
'client_case_id' => $clientCaseId,
'contract_id' => $account->contract_id,
]);
// Link the payment to the activity
$payment->update(['activity_id' => $activity->id]);
}
}
return back()->with('success', 'Payment created.');
}
public function destroy(Account $account, Payment $payment): RedirectResponse|JsonResponse
{
if ($payment->account_id !== $account->id) {
abort(404);
}
// Delete related booking(s) to revert balance via model events
Booking::query()->where('payment_id', $payment->id)->get()->each->delete();
// Optionally delete related activity
if ($payment->activity_id ?? null) {
$activity = Activity::query()->find($payment->activity_id);
if ($activity) {
$activity->delete();
}
}
$payment->delete();
if (request()->wantsJson()) {
$account->refresh();
return response()->json([
'ok' => true,
'balance_amount' => $account->balance_amount,
]);
}
return back()->with('success', 'Payment deleted.');
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UpdatePaymentSettingRequest;
use App\Models\Action;
use App\Models\Decision;
use App\Models\PaymentSetting;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PaymentSettingController extends Controller
{
public function edit(): Response
{
$setting = PaymentSetting::query()->first();
if (! $setting) {
$setting = PaymentSetting::query()->create([
'default_currency' => 'EUR',
'create_activity_on_payment' => false,
'default_decision_id' => null,
'default_action_id' => null,
'activity_note_template' => 'Prejeto plačilo: {amount} {currency}',
]);
}
$decisions = Decision::query()->orderBy('name')->get(['id', 'name']);
$actions = Action::query()
->with(['decisions:id'])
->orderBy('name')
->get()
->map(function (Action $a) {
return [
'id' => $a->id,
'name' => $a->name,
'decision_ids' => $a->decisions->pluck('id')->values(),
];
});
return Inertia::render('Settings/Payments/Index', [
'setting' => [
'id' => $setting->id,
'default_currency' => $setting->default_currency,
'create_activity_on_payment' => (bool) $setting->create_activity_on_payment,
'default_decision_id' => $setting->default_decision_id,
'default_action_id' => $setting->default_action_id,
'activity_note_template' => $setting->activity_note_template,
],
'decisions' => $decisions,
'actions' => $actions,
]);
}
public function update(UpdatePaymentSettingRequest $request): RedirectResponse
{
$data = $request->validated();
$setting = PaymentSetting::query()->firstOrFail();
// Ensure boolean cast for checkbox
$data['create_activity_on_payment'] = (bool) ($data['create_activity_on_payment'] ?? false);
$setting->fill($data)->save();
return back()->with('success', 'Payment settings updated.');
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBookingRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'amount' => ['required', 'numeric', 'min:0.01'],
'type' => ['required', 'in:debit,credit'],
'description' => ['nullable', 'string', 'max:255'],
'booked_at' => ['nullable', 'date'],
'payment_id' => ['nullable', 'integer', 'exists:payments,id'],
];
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePaymentRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'amount' => ['required', 'numeric', 'min:0.01'],
'currency' => ['nullable', 'string', 'size:3'],
'reference' => ['nullable', 'string', 'max:100'],
'paid_at' => ['nullable', 'date'],
'meta' => ['nullable', 'array'],
];
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePaymentSettingRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'default_currency' => ['required', 'string', 'size:3'],
'create_activity_on_payment' => ['sometimes', 'boolean'],
'default_decision_id' => ['nullable', 'integer', 'exists:decisions,id'],
'default_action_id' => ['nullable', 'integer', 'exists:actions,id'],
'activity_note_template' => ['nullable', 'string', 'max:255'],
];
}
}