32 - Backend: Tracker/Entry models, TrackerController, EntryController, update routes
This commit is contained in:
parent
b66e018b3a
commit
22e3394cb1
14 changed files with 319 additions and 211 deletions
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
|
@ -11,36 +12,7 @@ class AssetController extends Controller
|
|||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$assets = Asset::orderBy('symbol')->get();
|
||||
|
||||
return response()->json($assets);
|
||||
}
|
||||
|
||||
public function current(): JsonResponse
|
||||
{
|
||||
$user = User::default();
|
||||
|
||||
return response()->json([
|
||||
'asset' => $user->asset,
|
||||
'price_tracking_enabled' => $user->price_tracking_enabled,
|
||||
]);
|
||||
}
|
||||
|
||||
public function setCurrent(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'symbol' => 'required|string|max:10',
|
||||
'full_name' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$asset = Asset::findOrCreateBySymbol(
|
||||
$validated['symbol'],
|
||||
$validated['full_name'] ?? null
|
||||
);
|
||||
|
||||
User::default()->update(['asset_id' => $asset->id]);
|
||||
|
||||
return back()->with('success', 'Asset set successfully!');
|
||||
return response()->json(Asset::orderBy('symbol')->get());
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
|
|
@ -65,11 +37,10 @@ public function store(Request $request): JsonResponse
|
|||
public function show(Asset $asset): JsonResponse
|
||||
{
|
||||
$asset->load('assetPrices');
|
||||
$currentPrice = $asset->currentPrice();
|
||||
|
||||
return response()->json([
|
||||
'asset' => $asset,
|
||||
'current_price' => $currentPrice,
|
||||
'current_price' => $asset->currentPrice(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Milestones;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Milestone;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -17,15 +19,25 @@ public function store(Request $request): RedirectResponse
|
|||
'description' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
Milestone::create($validated);
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker) {
|
||||
return back()->withErrors(['tracker' => 'No tracker found. Please complete onboarding first.']);
|
||||
}
|
||||
|
||||
$tracker->milestones()->create($validated);
|
||||
|
||||
return back()->with('success', 'Milestone created successfully');
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$milestones = Milestone::orderBy('target')->get();
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
return response()->json($milestones);
|
||||
if (! $tracker) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
return response()->json($tracker->milestones()->orderBy('target')->get());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pricing;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Pricing\AssetPrice;
|
||||
use App\Models\Tracker;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PricingController extends Controller
|
||||
{
|
||||
private User $user;
|
||||
private ?Tracker $tracker;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->user = User::default();
|
||||
$this->tracker = User::default()->tracker;
|
||||
}
|
||||
|
||||
public function current(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'current_price' => AssetPrice::current($this->user->asset_id),
|
||||
'current_price' => AssetPrice::current($this->tracker?->asset_id),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -31,14 +34,14 @@ public function update(Request $request)
|
|||
'price' => 'required|numeric|min:0.0001',
|
||||
]);
|
||||
|
||||
if (! $this->user->asset_id) {
|
||||
if (! $this->tracker?->asset_id) {
|
||||
return back()->withErrors(['asset' => 'Please set an asset first.']);
|
||||
}
|
||||
|
||||
AssetPrice::updatePrice($this->user->asset_id, $validated['date'], $validated['price']);
|
||||
AssetPrice::updatePrice($this->tracker->asset_id, $validated['date'], $validated['price']);
|
||||
|
||||
if (! $this->user->price_tracking_enabled) {
|
||||
$this->user->update(['price_tracking_enabled' => true]);
|
||||
if (! $this->tracker->price_tracking_enabled) {
|
||||
$this->tracker->update(['price_tracking_enabled' => true]);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Asset price updated successfully!');
|
||||
|
|
@ -48,7 +51,7 @@ public function history(Request $request): JsonResponse
|
|||
{
|
||||
$limit = min(max(1, $request->integer('limit', 30)), 365);
|
||||
|
||||
return response()->json(AssetPrice::history($this->user->asset_id, $limit));
|
||||
return response()->json(AssetPrice::history($this->tracker?->asset_id, $limit));
|
||||
}
|
||||
|
||||
public function forDate(Request $request, string $date): JsonResponse
|
||||
|
|
@ -57,7 +60,7 @@ public function forDate(Request $request, string $date): JsonResponse
|
|||
|
||||
return response()->json([
|
||||
'date' => $date,
|
||||
'price' => AssetPrice::forDate($date, $this->user->asset_id),
|
||||
'price' => AssetPrice::forDate($date, $this->tracker?->asset_id),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
93
app/Http/Controllers/TrackerController.php
Normal file
93
app/Http/Controllers/TrackerController.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrackerController extends Controller
|
||||
{
|
||||
public function show(): JsonResponse
|
||||
{
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker) {
|
||||
return response()->json(null);
|
||||
}
|
||||
|
||||
return response()->json($tracker->load('asset'));
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'label' => 'required|string|max:255',
|
||||
'unit' => 'required|string|max:50',
|
||||
'price_tracking_enabled' => 'boolean',
|
||||
'symbol' => 'nullable|string|max:10',
|
||||
'full_name' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$user = User::default();
|
||||
|
||||
$assetId = null;
|
||||
if (! empty($validated['symbol'])) {
|
||||
$asset = Asset::findOrCreateBySymbol($validated['symbol'], $validated['full_name'] ?? null);
|
||||
$assetId = $asset->id;
|
||||
}
|
||||
|
||||
if ($user->tracker) {
|
||||
return response()->json(['error' => 'Tracker already exists.'], 409);
|
||||
}
|
||||
|
||||
$tracker = $user->tracker()->create([
|
||||
'label' => $validated['label'],
|
||||
'unit' => $validated['unit'],
|
||||
'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? false,
|
||||
'asset_id' => $assetId,
|
||||
]);
|
||||
|
||||
return response()->json($tracker->load('asset'), 201);
|
||||
}
|
||||
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'label' => 'sometimes|string|max:255',
|
||||
'unit' => 'sometimes|string|max:50',
|
||||
'price_tracking_enabled' => 'sometimes|boolean',
|
||||
'symbol' => 'nullable|string|max:10',
|
||||
'full_name' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker) {
|
||||
return response()->json(['error' => 'No tracker found.'], 404);
|
||||
}
|
||||
|
||||
if (array_key_exists('symbol', $validated)) {
|
||||
if ($validated['symbol']) {
|
||||
$asset = Asset::findOrCreateBySymbol($validated['symbol'], $validated['full_name'] ?? null);
|
||||
$tracker->asset_id = $asset->id;
|
||||
} else {
|
||||
$tracker->asset_id = null;
|
||||
}
|
||||
}
|
||||
|
||||
$update = array_filter([
|
||||
'label' => $validated['label'] ?? null,
|
||||
'unit' => $validated['unit'] ?? null,
|
||||
'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? null,
|
||||
'asset_id' => $tracker->asset_id,
|
||||
], fn ($v) => $v !== null);
|
||||
|
||||
$tracker->update($update);
|
||||
|
||||
return response()->json($tracker->load('asset'));
|
||||
}
|
||||
}
|
||||
91
app/Http/Controllers/Transactions/EntryController.php
Normal file
91
app/Http/Controllers/Transactions/EntryController.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Transactions;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Transactions\Entry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EntryController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
return response()->json($tracker->entries()->orderBy('date', 'desc')->get());
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'date' => 'required|date|before_or_equal:today',
|
||||
'quantity' => 'required|numeric|min:0.000001',
|
||||
'unit_price' => 'nullable|numeric|min:0.01',
|
||||
'total_cost' => 'nullable|numeric|min:0.01',
|
||||
]);
|
||||
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker) {
|
||||
return back()->withErrors(['tracker' => 'No tracker found. Please complete onboarding first.']);
|
||||
}
|
||||
|
||||
// If unit_price and total_cost provided, verify the calculation
|
||||
if (isset($validated['unit_price'], $validated['total_cost'])) {
|
||||
$calculatedTotal = $validated['quantity'] * $validated['unit_price'];
|
||||
if (abs($calculatedTotal - $validated['total_cost']) > 0.01) {
|
||||
return back()->withErrors([
|
||||
'total_cost' => 'Total cost does not match quantity × unit price.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$tracker->entries()->create($validated);
|
||||
|
||||
return back()->with('success', 'Entry added successfully!');
|
||||
}
|
||||
|
||||
public function summary(): JsonResponse
|
||||
{
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker) {
|
||||
return response()->json([
|
||||
'total_quantity' => 0,
|
||||
'total_cost' => 0,
|
||||
'average_cost_per_unit' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'total_quantity' => Entry::totalQuantity($tracker->id),
|
||||
'total_cost' => Entry::totalCost($tracker->id),
|
||||
'average_cost_per_unit' => Entry::averageCostPerUnit($tracker->id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Entry $entry): JsonResponse
|
||||
{
|
||||
$tracker = User::default()->tracker;
|
||||
|
||||
if (! $tracker || $entry->tracker_id !== $tracker->id) {
|
||||
return response()->json(['error' => 'Entry not found.'], 404);
|
||||
}
|
||||
|
||||
$entry->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Entry deleted successfully!',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Transactions;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Transactions\Purchase;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PurchaseController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$purchases = Purchase::orderBy('date', 'desc')->get();
|
||||
|
||||
return response()->json($purchases);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'date' => 'required|date|before_or_equal:today',
|
||||
'shares' => 'required|numeric|min:0.000001',
|
||||
'price_per_share' => 'required|numeric|min:0.01',
|
||||
'total_cost' => 'required|numeric|min:0.01',
|
||||
]);
|
||||
|
||||
// Verify calculation is correct
|
||||
$calculatedTotal = $validated['shares'] * $validated['price_per_share'];
|
||||
if (abs($calculatedTotal - $validated['total_cost']) > 0.01) {
|
||||
return back()->withErrors([
|
||||
'total_cost' => 'Total cost does not match shares × price per share.',
|
||||
]);
|
||||
}
|
||||
|
||||
Purchase::create([
|
||||
'date' => $validated['date'],
|
||||
'shares' => $validated['shares'],
|
||||
'price_per_share' => $validated['price_per_share'],
|
||||
'total_cost' => $validated['total_cost'],
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Purchase added successfully!');
|
||||
}
|
||||
|
||||
public function summary()
|
||||
{
|
||||
$totalShares = Purchase::totalShares();
|
||||
$totalInvestment = Purchase::totalInvestment();
|
||||
$averageCost = Purchase::averageCostPerShare();
|
||||
|
||||
return response()->json([
|
||||
'total_shares' => $totalShares,
|
||||
'total_investment' => $totalInvestment,
|
||||
'average_cost_per_share' => $averageCost,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified purchase.
|
||||
*/
|
||||
public function destroy(Purchase $purchase)
|
||||
{
|
||||
$purchase->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Purchase deleted successfully!',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,11 +35,6 @@ public function assetPrices(): HasMany
|
|||
return $this->hasMany(Pricing\AssetPrice::class);
|
||||
}
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
|
||||
public function currentPrice(): ?float
|
||||
{
|
||||
$latestPrice = $this->assetPrices()->latest('date')->first();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @method static create(array $array)
|
||||
|
|
@ -19,4 +20,9 @@ class Milestone extends Model
|
|||
protected $casts = [
|
||||
'target' => 'integer',
|
||||
];
|
||||
|
||||
public function tracker(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tracker::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Transactions\Entry;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Tracker extends Model
|
||||
{
|
||||
|
|
@ -22,4 +25,24 @@ protected function casts(): array
|
|||
'price_tracking_enabled' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function asset(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Asset::class);
|
||||
}
|
||||
|
||||
public function entries(): HasMany
|
||||
{
|
||||
return $this->hasMany(Entry::class);
|
||||
}
|
||||
|
||||
public function milestones(): HasMany
|
||||
{
|
||||
return $this->hasMany(Milestone::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
app/Models/Transactions/Entry.php
Normal file
53
app/Models/Transactions/Entry.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Transactions;
|
||||
|
||||
use App\Models\Tracker;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Entry extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'tracker_id',
|
||||
'date',
|
||||
'quantity',
|
||||
'unit_price',
|
||||
'total_cost',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date',
|
||||
'quantity' => 'decimal:6',
|
||||
'unit_price' => 'decimal:4',
|
||||
'total_cost' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
public function tracker(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tracker::class);
|
||||
}
|
||||
|
||||
public static function totalQuantity(int $trackerId): float
|
||||
{
|
||||
return (float) static::where('tracker_id', $trackerId)->sum('quantity');
|
||||
}
|
||||
|
||||
public static function totalCost(int $trackerId): float
|
||||
{
|
||||
return (float) static::where('tracker_id', $trackerId)->sum('total_cost');
|
||||
}
|
||||
|
||||
public static function averageCostPerUnit(int $trackerId): float
|
||||
{
|
||||
$totalQuantity = static::totalQuantity($trackerId);
|
||||
$totalCost = static::totalCost($trackerId);
|
||||
|
||||
return $totalQuantity > 0 ? $totalCost / $totalQuantity : 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Transactions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Purchase extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'date',
|
||||
'shares',
|
||||
'price_per_share',
|
||||
'total_cost',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'date',
|
||||
'shares' => 'decimal:6',
|
||||
'price_per_share' => 'decimal:4',
|
||||
'total_cost' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Calculate total shares
|
||||
*/
|
||||
public static function totalShares(): float
|
||||
{
|
||||
return static::sum('shares');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total investment
|
||||
*/
|
||||
public static function totalInvestment(): float
|
||||
{
|
||||
return static::sum('total_cost');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average cost per share
|
||||
*/
|
||||
public static function averageCostPerShare(): float
|
||||
{
|
||||
$totalShares = static::totalShares();
|
||||
$totalCost = static::totalInvestment();
|
||||
|
||||
return $totalShares > 0 ? $totalCost / $totalShares : 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,40 +2,23 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Models\Transactions\Purchase;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @property int|null $asset_id
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'asset_id',
|
||||
'price_tracking_enabled',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
|
|
@ -46,13 +29,12 @@ protected function casts(): array
|
|||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'price_tracking_enabled' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function asset(): BelongsTo
|
||||
public function tracker(): HasOne
|
||||
{
|
||||
return $this->belongsTo(Asset::class);
|
||||
return $this->hasOne(Tracker::class);
|
||||
}
|
||||
|
||||
public static function default(): self
|
||||
|
|
@ -67,16 +49,16 @@ public static function default(): self
|
|||
|
||||
public function hasCompletedOnboarding(): bool
|
||||
{
|
||||
return $this->hasPurchases() && $this->hasMilestones();
|
||||
return $this->hasEntries() && $this->hasMilestones();
|
||||
}
|
||||
|
||||
public function hasPurchases(): bool
|
||||
public function hasEntries(): bool
|
||||
{
|
||||
return Purchase::totalShares() > 0;
|
||||
return (bool) $this->tracker?->entries()->exists();
|
||||
}
|
||||
|
||||
public function hasMilestones(): bool
|
||||
{
|
||||
return Milestone::count() > 0;
|
||||
return (bool) $this->tracker?->milestones()->exists();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
use App\Http\Controllers\AssetController;
|
||||
use App\Http\Controllers\Milestones\MilestoneController;
|
||||
use App\Http\Controllers\Pricing\PricingController;
|
||||
use App\Http\Controllers\Transactions\PurchaseController;
|
||||
use App\Http\Controllers\TrackerController;
|
||||
use App\Http\Controllers\Transactions\EntryController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
|
||||
|
|
@ -15,22 +16,25 @@
|
|||
return Inertia::render('dashboard');
|
||||
})->name('dashboard');
|
||||
|
||||
// Tracker routes
|
||||
Route::get('/tracker', [TrackerController::class, 'show'])->name('tracker.show');
|
||||
Route::post('/tracker', [TrackerController::class, 'store'])->name('tracker.store');
|
||||
Route::patch('/tracker', [TrackerController::class, 'update'])->name('tracker.update');
|
||||
|
||||
// Asset routes
|
||||
Route::prefix('assets')->name('assets.')->group(function () {
|
||||
Route::get('/', [AssetController::class, 'index'])->name('index');
|
||||
Route::post('/', [AssetController::class, 'store'])->name('store');
|
||||
Route::get('/current', [AssetController::class, 'current'])->name('current');
|
||||
Route::post('/set-current', [AssetController::class, 'setCurrent'])->name('set-current');
|
||||
Route::get('/search', [AssetController::class, 'search'])->name('search');
|
||||
Route::get('/{asset}', [AssetController::class, 'show'])->name('show');
|
||||
});
|
||||
|
||||
// Purchase routes
|
||||
Route::prefix('purchases')->name('purchases.')->group(function () {
|
||||
Route::get('/', [PurchaseController::class, 'index'])->name('index');
|
||||
Route::post('/', [PurchaseController::class, 'store'])->name('store');
|
||||
Route::get('/summary', [PurchaseController::class, 'summary'])->name('summary');
|
||||
Route::delete('/{purchase}', [PurchaseController::class, 'destroy'])->name('destroy');
|
||||
// Entry routes (replaces purchases)
|
||||
Route::prefix('entries')->name('entries.')->group(function () {
|
||||
Route::get('/', [EntryController::class, 'index'])->name('index');
|
||||
Route::post('/', [EntryController::class, 'store'])->name('store');
|
||||
Route::get('/summary', [EntryController::class, 'summary'])->name('summary');
|
||||
Route::delete('/{entry}', [EntryController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
|
||||
// Pricing routes
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@ class MilestoneTest extends TestCase
|
|||
|
||||
private function tracker(): Tracker
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
return Tracker::create([
|
||||
'user_id' => $user->id,
|
||||
'user_id' => User::default()->id,
|
||||
'label' => 'Test',
|
||||
'unit' => 'units',
|
||||
]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue