feat(store): complete partner store overhaul with mapping, household search, financial analytics, and resident notification system
This commit is contained in:
@@ -347,7 +347,7 @@ class AdminPartnerStoreController extends ApiController
|
||||
// For now, we'll return the data needed to render a printable QR.
|
||||
return $this->ok([
|
||||
'serial' => $qrCode->serial,
|
||||
'qr_data' => route('qr.verify', ['serial' => $qrCode->serial]),
|
||||
'qr_data' => route('api.v1.admin.qr-codes.show', ['serial' => $qrCode->serial]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -365,6 +365,7 @@ class AdminPartnerStoreController extends ApiController
|
||||
'total_commission_pesos' => number_format($totalCommission / 100, 2),
|
||||
'total_settled_pesos' => number_format($totalSettled / 100, 2),
|
||||
'balance_due_pesos' => number_format($balanceDue / 100, 2),
|
||||
'gross_profit_pesos' => number_format(($totalRetailRevenue - $totalWholesaleCost) / 100, 2),
|
||||
'total_issued_codes' => (int) $store->purchases()->sum('quantity'),
|
||||
'total_sold_codes' => (int) $store->sales()->sum('quantity'),
|
||||
]);
|
||||
|
||||
@@ -4,11 +4,14 @@ namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Responses\ApiResponse;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
abstract class ApiController extends Controller
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
protected function ok(mixed $data = null, ?string $message = null, array $meta = []): JsonResponse
|
||||
{
|
||||
return ApiResponse::success($data, $message, $meta);
|
||||
|
||||
79
app/Http/Controllers/Api/V1/Me/MySalesController.php
Normal file
79
app/Http/Controllers/Api/V1/Me/MySalesController.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Me;
|
||||
|
||||
use App\Http\Controllers\Api\V1\ApiController;
|
||||
use App\Models\Household;
|
||||
use App\Models\StoreSale;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class MySalesController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Get the household associated with the authenticated user.
|
||||
*/
|
||||
private function getHousehold(): ?Household
|
||||
{
|
||||
return Household::where('head_user_id', auth()->id())
|
||||
->orWhereHas('members', function ($q) {
|
||||
$q->where('user_id', auth()->id());
|
||||
})
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* List sales history for the user's household.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$household = $this->getHousehold();
|
||||
|
||||
if (!$household) {
|
||||
return $this->ok([], 'No household associated with this account.');
|
||||
}
|
||||
|
||||
$sales = StoreSale::where('household_id', $household->id)
|
||||
->with('store')
|
||||
->latest('sold_at')
|
||||
->paginate(15);
|
||||
|
||||
return $this->ok(
|
||||
$sales->map(fn($s) => [
|
||||
'id' => $s->id,
|
||||
'store_name' => $s->store?->business_name ?? 'Verde Partner Store',
|
||||
'quantity' => $s->quantity,
|
||||
'retail_price_pesos' => number_format($s->retail_price_centavos / 100, 2),
|
||||
'sold_at' => $s->sold_at?->toIso8601String() ?? $s->created_at->toIso8601String(),
|
||||
'receipt_url' => route('api.v1.me.sales.receipt', $s->id),
|
||||
]),
|
||||
null,
|
||||
[
|
||||
'page' => $sales->currentPage(),
|
||||
'per_page' => $sales->perPage(),
|
||||
'total' => $sales->total(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a PDF receipt for a specific sale.
|
||||
*/
|
||||
public function downloadReceipt(StoreSale $sale): Response
|
||||
{
|
||||
$household = $this->getHousehold();
|
||||
|
||||
if (!$household || $sale->household_id !== $household->id) {
|
||||
abort(403, 'Unauthorized access to this receipt.');
|
||||
}
|
||||
|
||||
$sale->load(['store', 'household.head', 'household.barangay']);
|
||||
|
||||
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pdfs.receipt', [
|
||||
'sale' => $sale
|
||||
]);
|
||||
|
||||
return $pdf->download("receipt-{$sale->id}.pdf");
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ namespace App\Http\Controllers\Api\V1\Store;
|
||||
|
||||
use App\Http\Controllers\Api\V1\ApiController;
|
||||
use App\Models\PartnerStore;
|
||||
use App\Models\StoreSale;
|
||||
use App\Models\User;
|
||||
use App\Services\Store\StoreOperations;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class StorePortalController extends ApiController
|
||||
{
|
||||
@@ -155,11 +157,26 @@ class StorePortalController extends ApiController
|
||||
return $this->ok($history);
|
||||
}
|
||||
|
||||
public function downloadReceipt(\App\Models\StoreSale $sale): Response
|
||||
{
|
||||
$this->authorize('view', $sale);
|
||||
|
||||
$sale->load(['store', 'household.head', 'household.barangay']);
|
||||
|
||||
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pdfs.receipt', [
|
||||
'sale' => $sale
|
||||
]);
|
||||
|
||||
return $pdf->download("receipt-{$sale->id}.pdf");
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for households to record a sale.
|
||||
* Search for households to record a sale. Scoped to the store's LGU.
|
||||
*/
|
||||
public function searchHouseholds(Request $request): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
|
||||
$request->validate([
|
||||
'q' => 'required|string|min:2',
|
||||
]);
|
||||
@@ -167,18 +184,46 @@ class StorePortalController extends ApiController
|
||||
$term = '%' . $request->string('q') . '%';
|
||||
|
||||
$households = \App\Models\Household::query()
|
||||
->with('head')
|
||||
->with(['head', 'barangay'])
|
||||
->where('tenant_id', $store->tenant_id)
|
||||
->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);
|
||||
})
|
||||
->orWhereHas('members', function($m) use ($term) {
|
||||
$m->where('first_name', 'like', $term)
|
||||
->orWhere('last_name', 'like', $term);
|
||||
});
|
||||
})
|
||||
->limit(10)
|
||||
->limit(15)
|
||||
->get();
|
||||
|
||||
return $this->ok($households);
|
||||
return $this->ok($households->map(fn($h) => [
|
||||
'id' => $h->id,
|
||||
'uuid' => $h->uuid,
|
||||
'head_name' => $h->head?->full_name ?? 'Unknown Head',
|
||||
'address' => $h->address_line,
|
||||
'barangay' => $h->barangay?->name,
|
||||
'lat' => $h->coordinates?->latitude,
|
||||
'lng' => $h->coordinates?->longitude,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get store settings and pricing.
|
||||
*/
|
||||
public function getSettings(): JsonResponse
|
||||
{
|
||||
$store = $this->getStore();
|
||||
$tenant = $store->tenant;
|
||||
|
||||
return $this->ok([
|
||||
'retail_price_centavos' => (int) ($tenant->qr_retail_price_centavos ?? 1000),
|
||||
'retail_price_pesos' => ($tenant->qr_retail_price_centavos ?? 1000) / 100,
|
||||
'commission_rate_percent' => (int) $store->commission_rate_percent,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user