feat: implement team standby status, helper persistence, and store portal foundations
This commit is contained in:
@@ -108,6 +108,7 @@ class AdminPartnerStoreController extends ApiController
|
||||
$data = $request->validate([
|
||||
'quantity' => ['required', 'integer', 'min:1', 'max:50000'],
|
||||
'wholesale_price_centavos' => ['required', 'integer', 'min:0'],
|
||||
'is_prepaid' => ['nullable', 'boolean'],
|
||||
]);
|
||||
|
||||
if ($store->status !== PartnerStore::STATUS_ACTIVE) {
|
||||
@@ -119,7 +120,8 @@ class AdminPartnerStoreController extends ApiController
|
||||
(int) $data['quantity'],
|
||||
(int) $data['wholesale_price_centavos'],
|
||||
null,
|
||||
$request->user()
|
||||
$request->user(),
|
||||
(bool) ($data['is_prepaid'] ?? false)
|
||||
);
|
||||
|
||||
return $this->created([
|
||||
|
||||
@@ -20,7 +20,7 @@ class AdminTeamController extends ApiController
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'status' => ['nullable', 'in:active,inactive'],
|
||||
'status' => ['nullable', 'in:active,inactive,standby'],
|
||||
'area_id' => ['nullable', 'integer'],
|
||||
'q' => ['nullable', 'string', 'max:100'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:500'],
|
||||
@@ -28,7 +28,7 @@ class AdminTeamController extends ApiController
|
||||
$perPage = (int) $request->input('per_page', 25);
|
||||
|
||||
$teams = CollectionTeam::query()
|
||||
->with(['area', 'driver', 'scanner', 'truck'])
|
||||
->with(['area', 'driver', 'scanner', 'truck', 'helpers.user', 'currentTrip'])
|
||||
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
|
||||
->when($request->filled('area_id'), fn ($q) => $q->where('area_id', $request->integer('area_id')))
|
||||
->when($request->filled('q'), fn ($q) => $q->where('name', 'like', '%'.$request->string('q').'%'))
|
||||
@@ -77,7 +77,7 @@ class AdminTeamController extends ApiController
|
||||
}
|
||||
}
|
||||
|
||||
unset($data['helper_ids'], $data['override_conflicts'], $data['helper_count'], $data['helper_assignment_mode']);
|
||||
unset($data['helper_ids'], $data['override_conflicts'], $data['helper_assignment_mode']);
|
||||
|
||||
$team = DB::transaction(function () use ($data, $helperIds) {
|
||||
$team = CollectionTeam::create($data);
|
||||
@@ -105,7 +105,7 @@ class AdminTeamController extends ApiController
|
||||
|
||||
public function show(CollectionTeam $team): JsonResponse
|
||||
{
|
||||
$team->load(['area', 'driver', 'scanner', 'truck', 'helpers.user']);
|
||||
$team->load(['area', 'driver', 'scanner', 'truck', 'helpers.user', 'currentTrip']);
|
||||
|
||||
return $this->ok(new CollectionTeamResource($team));
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class AdminTeamController extends ApiController
|
||||
}
|
||||
}
|
||||
|
||||
unset($data['helper_ids'], $data['override_conflicts'], $data['helper_count'], $data['helper_assignment_mode']);
|
||||
unset($data['helper_ids'], $data['override_conflicts'], $data['helper_assignment_mode']);
|
||||
|
||||
DB::transaction(function () use ($team, $data, $helperIds) {
|
||||
$team->update($data);
|
||||
|
||||
@@ -35,6 +35,8 @@ class TripDetourController extends Controller
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$trip->update(['is_detouring' => true]);
|
||||
|
||||
return response()->json(['message' => 'Detour to dumpsite logged.']);
|
||||
}
|
||||
|
||||
@@ -63,6 +65,8 @@ class TripDetourController extends Controller
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$trip->update(['is_detouring' => false]);
|
||||
|
||||
return response()->json(['message' => 'Resuming route logged.']);
|
||||
}
|
||||
}
|
||||
|
||||
184
app/Http/Controllers/Api/V1/Store/StorePortalController.php
Normal file
184
app/Http/Controllers/Api/V1/Store/StorePortalController.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Store;
|
||||
|
||||
use App\Http\Controllers\Api\V1\ApiController;
|
||||
use App\Models\PartnerStore;
|
||||
use App\Models\User;
|
||||
use App\Services\Store\StoreOperations;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StorePortalController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Get the store associated with the authenticated user.
|
||||
*/
|
||||
protected function getStore(): PartnerStore
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$store = PartnerStore::where('owner_user_id', $user->id)->first();
|
||||
|
||||
if (!$store) {
|
||||
abort(403, 'No partner store associated with this account.');
|
||||
}
|
||||
|
||||
return $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard summary data.
|
||||
*/
|
||||
public function dashboard(): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
$inventory = $store->inventory;
|
||||
|
||||
$data = [
|
||||
'business_name' => $store->business_name,
|
||||
'inventory_balance' => $inventory ? (int) $inventory->current_code_balance : 0,
|
||||
'prepaid_balance' => $inventory ? (int) $inventory->prepaid_balance : 0,
|
||||
'total_sales_count' => (int) $store->sales()->count(),
|
||||
'status' => $store->status,
|
||||
];
|
||||
|
||||
return $this->ok($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get financial analytics for the store.
|
||||
*/
|
||||
public function analytics(): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
// Sums
|
||||
$totalRetail = $store->sales()->sum('retail_price_centavos');
|
||||
$totalWholesale = $store->purchases()->sum('wholesale_price_centavos');
|
||||
$totalCommission = $store->sales()->sum('commission_centavos');
|
||||
|
||||
// Calculate settlement balance
|
||||
$totalSettled = $store->settlements()->sum('amount_centavos');
|
||||
$amountOwed = ($totalRetail - $totalCommission) - $totalSettled;
|
||||
|
||||
return $this->ok([
|
||||
'total_retail_revenue_centavos' => (int) $totalRetail,
|
||||
'total_wholesale_cost_centavos' => (int) $totalWholesale,
|
||||
'total_commission_earned_centavos' => (int) $totalCommission,
|
||||
'amount_owed_to_lgu_centavos' => (int) $amountOwed,
|
||||
'gross_profit_centavos' => (int) ($totalRetail - $totalWholesale),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a retail sale to a resident.
|
||||
*/
|
||||
public function recordSale(Request $request, StoreOperations $operations): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$request->validate([
|
||||
'household_id' => 'required|exists:households,id',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
'serial_number' => 'nullable|string|exists:qr_codes,serial',
|
||||
]);
|
||||
|
||||
try {
|
||||
$sale = $operations->sellToHousehold(
|
||||
$store,
|
||||
\App\Models\Household::findOrFail($request->household_id),
|
||||
$request->quantity,
|
||||
config('qr.default_retail_price_per_code_centavos'),
|
||||
$request->serial_number
|
||||
);
|
||||
|
||||
// Fetch the serials that were just activated for this sale
|
||||
$codes = \App\Models\QrCode::whereIn('id', $sale->id ? [$sale->id] : []) // Wait, StoreSale doesn't link to QrCode directly?
|
||||
// I need to find the codes that were activated.
|
||||
// QrCode table has assigned_to_household_id.
|
||||
->where('assigned_to_household_id', $request->household_id)
|
||||
->where('status', \App\States\QrCode\Active::$name)
|
||||
->latest('activated_at')
|
||||
->limit($request->quantity)
|
||||
->get();
|
||||
|
||||
$qrData = $codes->map(function ($code) {
|
||||
$qr = \Endroid\QrCode\QrCode::create($code->serial)
|
||||
->setSize(300)
|
||||
->setMargin(10);
|
||||
$writer = new \Endroid\QrCode\Writer\PngWriter();
|
||||
return [
|
||||
'serial' => $code->serial,
|
||||
'qr_base64' => base64_encode($writer->write($qr)->getString())
|
||||
];
|
||||
});
|
||||
|
||||
return $this->created([
|
||||
'sale' => $sale,
|
||||
'codes' => $qrData
|
||||
], 'Sale recorded successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), null, 422);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sales history for the store.
|
||||
*/
|
||||
public function salesHistory(): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$sales = $store->sales()
|
||||
->with('household.head')
|
||||
->latest()
|
||||
->paginate(15);
|
||||
|
||||
return $this->ok($sales);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inventory adjustment and purchase history.
|
||||
*/
|
||||
public function inventoryHistory(): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$history = \App\Models\StoreInventoryAdjustment::query()
|
||||
->where('store_id', $store->id)
|
||||
->with('user')
|
||||
->latest()
|
||||
->paginate(15);
|
||||
|
||||
return $this->ok($history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for households to record a sale.
|
||||
*/
|
||||
public function searchHouseholds(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'q' => 'required|string|min:2',
|
||||
]);
|
||||
|
||||
$term = '%' . $request->string('q') . '%';
|
||||
|
||||
$households = \App\Models\Household::query()
|
||||
->with('head')
|
||||
->where('verification_status', \App\Models\Household::VERIFICATION_APPROVED)
|
||||
->where(function($q) use ($term) {
|
||||
$q->where('address_line', 'like', $term)
|
||||
->orWhereHas('head', function($h) use ($term) {
|
||||
$h->where('first_name', 'like', $term)
|
||||
->orWhere('last_name', 'like', $term);
|
||||
});
|
||||
})
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
return $this->ok($households);
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,22 @@ class SuperAdminUserController extends ApiController
|
||||
'language' => $data['preferred_language'] ?? 'en',
|
||||
]);
|
||||
|
||||
// If store_partner, auto-create a shell store so they can access the dashboard
|
||||
if ($data['role'] === User::ROLE_STORE_PARTNER) {
|
||||
$store = \App\Models\PartnerStore::create([
|
||||
'tenant_id' => $user->tenant_id,
|
||||
'owner_user_id' => $user->id,
|
||||
'business_name' => $user->first_name . "'s Store",
|
||||
'status' => \App\Models\PartnerStore::STATUS_ACTIVE,
|
||||
'commission_rate_percent' => 10, // Default 10%
|
||||
]);
|
||||
|
||||
\App\Models\StoreInventory::create([
|
||||
'store_id' => $store->id,
|
||||
'current_code_balance' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
return $user;
|
||||
});
|
||||
|
||||
|
||||
29
app/Http/Controllers/Store/StoreDashboardController.php
Normal file
29
app/Http/Controllers/Store/StoreDashboardController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Store;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StoreDashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('store.dashboard');
|
||||
}
|
||||
|
||||
public function sales()
|
||||
{
|
||||
return view('store.sales');
|
||||
}
|
||||
|
||||
public function inventory()
|
||||
{
|
||||
return view('store.inventory');
|
||||
}
|
||||
|
||||
public function financials()
|
||||
{
|
||||
return view('store.financials');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user