This commit is contained in:
hackerESQ
2025-01-27 20:04:03 -06:00
parent 32bf256c84
commit ea22c27710
13 changed files with 569 additions and 81 deletions
@@ -3,6 +3,7 @@
namespace App\Http\ApiControllers;
use App\Models\Holding;
use App\Models\Portfolio;
use Illuminate\Http\Request;
use App\Http\Resources\HoldingResource;
use HackerEsq\FilterModels\FilterModels;
@@ -20,4 +21,16 @@ class HoldingController extends ApiController
return HoldingResource::collection($filters->paginated());
}
public function show(Portfolio $portfolio, string $symbol)
{
//
}
public function put(FilterModels $filters)
{
//
}
}
@@ -23,7 +23,9 @@ class TransactionController extends ApiController
}
public function store(TransactionRequest $request)
{
{
Gate::authorize('fullAccess', $request->portfolio);
$transaction = Transaction::create($request->validated());
return TransactionResource::make($transaction);
@@ -31,14 +33,14 @@ class TransactionController extends ApiController
public function show(Transaction $transaction)
{
Gate::authorize('readOnly', $transaction);
Gate::authorize('readOnly', $transaction->portfolio);
return TransactionResource::make($transaction);
}
public function update(TransactionRequest $request, Transaction $transaction)
{
Gate::authorize('fullAccess', $transaction);
Gate::authorize('fullAccess', $transaction->portfolio);
$transaction->update($request->validated());
@@ -47,7 +49,7 @@ class TransactionController extends ApiController
public function destroy(Transaction $transaction)
{
Gate::authorize('fullAccess', $transaction);
Gate::authorize('fullAccess', $transaction->portfolio);
$transaction->delete();
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
class FormRequest extends BaseFormRequest
{
public function requestOrModelValue($key, $model): mixed
{
return $this->request->get($key) ?? $this->{$model}?->{$key};
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\FormRequest;
class PortfolioRequest extends FormRequest
{
+41 -6
View File
@@ -2,11 +2,16 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Models\Portfolio;
use App\Http\Requests\FormRequest;
use App\Rules\SymbolValidationRule;
use App\Rules\QuantityValidationRule;
class TransactionRequest extends FormRequest
{
public ?Portfolio $portfolio;
/**
* Get the validation rules that apply to the request.
*
@@ -14,15 +19,45 @@ class TransactionRequest extends FormRequest
*/
public function rules(): array
{
$this->portfolio = Portfolio::findOrFail($this->requestOrModelValue('portfolio_id', 'transaction'));
$rules = [
'title' => ['required', 'string', 'min:5', 'max:255'],
'notes' => ['sometimes', 'nullable', 'string'],
'wishlist' => ['sometimes', 'nullable', 'boolean'],
'portfolio_id' => [], // validated by findOrFail() above
'symbol' => ['required', 'string', new SymbolValidationRule],
'transaction_type' => ['required', 'string', 'in:BUY,SELL'],
'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:' . now()->format('Y-m-d')],
'quantity' => [
'required',
'numeric',
'min:0',
new QuantityValidationRule(
$this->portfolio,
$this->requestOrModelValue('symbol', 'transaction'),
$this->requestOrModelValue('transaction_type', 'transaction'),
$this->requestOrModelValue('date', 'transaction')
)
],
'cost_basis' => ['exclude_if:transaction_type,SELL', 'min:0', 'numeric'],
'sale_price' => ['exclude_if:transaction_type,BUY', 'min:0', 'numeric'],
];
if (!is_null($this->portfolio)) {
$rules['title'][0] = 'sometimes';
if (!is_null($this->transaction)) {
$rules['symbol'][0] = 'sometimes';
$rules['transaction_type'][0] = 'sometimes';
$rules['date'][0] = 'sometimes';
$rules['quantity'][0] = 'sometimes';
if (
$this->requestOrModelValue('transaction_type', 'transaction') == 'SELL'
&& $this->requestOrModelValue('sale_price', 'transaction') == null
) {
$rules['sale_price'][0] = 'required';
} elseif (
$this->requestOrModelValue('transaction_type', 'transaction') == 'BUY'
&& $this->requestOrModelValue('cost_basis', 'transaction') == null
) {
$rules['cost_basis'][0] = 'required';
}
}
return $rules;
+43
View File
@@ -5,11 +5,13 @@ namespace App\Models;
use App\Models\AiChat;
use Carbon\CarbonPeriod;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
use App\Interfaces\MarketData\MarketDataInterface;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use App\Notifications\InvitedOnboardingNotification;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Portfolio extends Model
@@ -129,6 +131,7 @@ class Portfolio extends Model
// save
$portfolio->users()->sync($owner);
static::$owner_id = null;
}
}
@@ -253,4 +256,44 @@ class Portfolio extends Model
}
return $formattedHoldings;
}
/**
* Share a portfolio with a user
*
* @param string $email
* @param boolean $fullAccess
* @return void
*/
public function share(string $email, bool $fullAccess = false): void
{
$user = User::firstOrCreate([
'email' => $email
], [
'name' => Str::title(Str::before($email, '@'))
]);
$permissions[$user->id] = [
'full_access' => $fullAccess
];
$sync = $this->users()->syncWithoutDetaching($permissions);
if (!empty($sync['attached'])) {
foreach($sync['attached'] as $newUserId) {
User::find($newUserId)->notify(new InvitedOnboardingNotification($this, auth()->user()));
};
}
}
/**
* Un-share a portfolio
*
* @param string $userId
* @return void
*/
public function unShare(string $userId): void
{
$this->users()->detach($userId);
}
}
+9 -4
View File
@@ -13,10 +13,10 @@ class QuantityValidationRule implements ValidationRule
* @return void
*/
public function __construct(
protected Portfolio $portfolio,
protected string $symbol,
protected string $transactionType,
protected string $date
protected ?Portfolio $portfolio,
protected ?string $symbol,
protected ?string $transactionType,
protected ?string $date
) {
$this->portfolio = $portfolio;
$this->symbol = $symbol;
@@ -34,6 +34,11 @@ class QuantityValidationRule implements ValidationRule
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if (is_null($this->portfolio) || is_null($this->symbol) || is_null($this->transactionType) || is_null($this->date)) {
//
$fail(__('The quantity must not be greater than the available quantity.'));
}
if ($this->transactionType == 'SELL') {
$purchase_qty = $this->portfolio->transactions()