feat: integrate dynamic geofence fetching, service area spatial boundaries, partner store profiles & editing with Leaflet maps, custom store portal icons, QR distribution charts with geographic/LGU filters, and optimize admin dashboards

This commit is contained in:
Super Admin
2026-07-04 18:30:46 +08:00
parent 8d82674e8f
commit a2cf8c2c75
44 changed files with 3854 additions and 99 deletions

View File

@@ -8,6 +8,8 @@ use App\Models\Household;
use App\Models\PartnerStore;
use App\Models\QrCode;
use App\Models\StoreInventoryAdjustment;
use App\Models\StoreSale;
use App\Models\User;
use App\Services\Store\StoreOperations;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -146,9 +148,10 @@ class AdminPartnerStoreController extends ApiController
try {
$household = Household::findOrFail($data['household_id']);
$sale = $this->ops->sellToHousehold(
$sale = $this->ops->sell(
$store,
$household,
null,
(int) $data['quantity'],
$retailPrice,
);
@@ -370,4 +373,173 @@ class AdminPartnerStoreController extends ApiController
'total_sold_codes' => (int) $store->sales()->sum('quantity'),
]);
}
public function distributionChart(Request $request, PartnerStore $store): JsonResponse
{
$period = $request->input('period', 'daily');
$driver = \DB::getDriverName();
$isSqlite = $driver === 'sqlite';
$query = $store->sales();
switch ($period) {
case 'weekly':
$dateExpression = $isSqlite
? "strftime('%Y-W%W', COALESCE(sold_at, created_at))"
: "DATE_FORMAT(COALESCE(sold_at, created_at), '%x-W%v')";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->whereRaw("COALESCE(sold_at, created_at) >= ?", [now()->subWeeks(12)->startOfWeek()])
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
case 'monthly':
$dateExpression = $isSqlite
? "strftime('%Y-%m', COALESCE(sold_at, created_at))"
: "DATE_FORMAT(COALESCE(sold_at, created_at), '%Y-%m')";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->whereRaw("COALESCE(sold_at, created_at) >= ?", [now()->subMonths(12)->startOfMonth()])
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
case 'overall':
$dateExpression = $isSqlite
? "strftime('%Y-%m', COALESCE(sold_at, created_at))"
: "DATE_FORMAT(COALESCE(sold_at, created_at), '%Y-%m')";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
default: // daily — last 30 days
$dateExpression = $isSqlite
? "date(COALESCE(sold_at, created_at))"
: "DATE(COALESCE(sold_at, created_at))";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->whereRaw("COALESCE(sold_at, created_at) >= ?", [now()->subDays(29)->startOfDay()])
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
}
return $this->ok([
'period' => $period,
'labels' => $rows->pluck('label'),
'values' => $rows->pluck('total')->map(fn ($v) => (int) $v),
'total_distributed' => (int) $store->sales()->sum('quantity'),
]);
}
public function overallDistributionChart(Request $request): JsonResponse
{
$period = $request->input('period', 'daily');
$user = $request->user();
$isSuperAdmin = $user && $user->role === User::ROLE_SUPER_ADMIN;
$storeQuery = PartnerStore::query();
if ($isSuperAdmin) {
if ($request->filled('tenant_id')) {
$storeQuery->withoutGlobalScopes()->where('tenant_id', $request->integer('tenant_id'));
} else {
$storeQuery->withoutGlobalScopes();
}
} else {
if ($user && $user->tenant_id) {
$storeQuery->where('tenant_id', $user->tenant_id);
}
}
if ($request->filled('service_area_id')) {
$storeQuery->whereHas('barangay.serviceAreas', function ($q) use ($request) {
$q->where('service_areas.id', $request->integer('service_area_id'));
});
}
if ($request->filled('barangay_id')) {
$storeQuery->where('barangay_id', $request->integer('barangay_id'));
}
$storeIds = $storeQuery->pluck('id');
$driver = \DB::getDriverName();
$isSqlite = $driver === 'sqlite';
$query = StoreSale::query()->whereIn('store_id', $storeIds);
switch ($period) {
case 'weekly':
$dateExpression = $isSqlite
? "strftime('%Y-W%W', COALESCE(sold_at, created_at))"
: "DATE_FORMAT(COALESCE(sold_at, created_at), '%x-W%v')";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->whereRaw("COALESCE(sold_at, created_at) >= ?", [now()->subWeeks(12)->startOfWeek()])
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
case 'monthly':
$dateExpression = $isSqlite
? "strftime('%Y-%m', COALESCE(sold_at, created_at))"
: "DATE_FORMAT(COALESCE(sold_at, created_at), '%Y-%m')";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->whereRaw("COALESCE(sold_at, created_at) >= ?", [now()->subMonths(12)->startOfMonth()])
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
case 'overall':
$dateExpression = $isSqlite
? "strftime('%Y-%m', COALESCE(sold_at, created_at))"
: "DATE_FORMAT(COALESCE(sold_at, created_at), '%Y-%m')";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
default: // daily — last 30 days
$dateExpression = $isSqlite
? "date(COALESCE(sold_at, created_at))"
: "DATE(COALESCE(sold_at, created_at))";
$rows = $query
->selectRaw("{$dateExpression} as label, SUM(quantity) as total")
->whereRaw("COALESCE(sold_at, created_at) >= ?", [now()->subDays(29)->startOfDay()])
->groupByRaw($dateExpression)
->orderByRaw($dateExpression)
->get();
break;
}
return $this->ok([
'period' => $period,
'labels' => $rows->pluck('label'),
'values' => $rows->pluck('total')->map(fn ($v) => (int) $v),
'total_distributed' => (int) $query->sum('quantity'),
]);
}
}