Files
alrahma_sunday_school_api/app/Services/Inventory/InventorySummaryService.php
T
2026-06-08 23:45:55 -04:00

192 lines
7.1 KiB
PHP

<?php
namespace App\Services\Inventory;
use App\Models\InventoryItem;
use Illuminate\Support\Facades\DB;
class InventorySummaryService
{
public function __construct(private InventoryContextService $context)
{
}
public function summary(string $type, ?string $schoolYear): array
{
$type = strtolower($type);
$selectedYear = trim((string) ($schoolYear ?? $this->context->schoolYear()));
$items = InventoryItem::query()
->where('type', $type)
->orderBy('name')
->get()
->toArray();
$itemIds = array_map(static fn ($i) => (int) ($i['id'] ?? 0), $items);
$agg = [];
if (!empty($itemIds)) {
$qb = DB::table('inventory_movements')
->selectRaw('item_id,
SUM(CASE WHEN qty_change>0 THEN qty_change ELSE 0 END) AS received,
SUM(CASE WHEN qty_change<0 THEN -qty_change ELSE 0 END) AS issued')
->whereIn('item_id', $itemIds)
->groupBy('item_id');
if (strtolower($selectedYear) !== 'all' && $selectedYear !== '') {
$qb->where('school_year', $selectedYear);
}
foreach ($qb->get() as $row) {
$agg[(int) $row->item_id] = [
'received' => (int) ($row->received ?? 0),
'issued' => (int) ($row->issued ?? 0),
];
}
}
return [
'items' => $items,
'agg' => $agg,
'selectedYear' => $selectedYear,
'currentYear' => $this->context->schoolYear(),
'schoolYears' => $this->getSchoolYearsForType($type),
];
}
public function summaryAll(?string $schoolYear, ?string $type): array
{
$selectedYear = trim((string) ($schoolYear ?? $this->context->schoolYear()));
$selectedType = strtolower((string) ($type ?? 'all'));
$isAllYears = strtolower($selectedYear) === 'all';
$qbItems = DB::table('inventory_items as i')
->select('i.*', 'c.name AS category_name', DB::raw("CONCAT(u.firstname, ' ', u.lastname) AS updated_by_name"))
->leftJoin('inventory_categories as c', 'c.id', '=', 'i.category_id')
->leftJoin('users as u', 'u.id', '=', 'i.updated_by')
->orderBy('i.name');
if (!$isAllYears) {
$qbItems->where('i.school_year', $selectedYear);
}
if (in_array($selectedType, ['book', 'classroom', 'office', 'kitchen'], true)) {
$qbItems->where('i.type', $selectedType);
}
$items = $qbItems->get()->map(fn ($r) => (array) $r)->all();
$rows = [];
$totals = [
'opening' => 0,
'received' => 0,
'distributed' => 0,
'other_out' => 0,
'adjust_net' => 0,
'ending' => 0,
'onhand' => 0,
'variance' => 0,
];
if (!empty($items)) {
$itemIds = array_map('intval', array_column($items, 'id'));
$aggById = [];
$qb = DB::table('inventory_movements as m')
->selectRaw("
m.item_id,
SUM(CASE WHEN m.movement_type = 'initial' THEN m.qty_change ELSE 0 END) AS opening,
SUM(CASE WHEN m.qty_change > 0 AND m.movement_type IN ('in','adjust') THEN m.qty_change ELSE 0 END) AS received,
SUM(CASE WHEN m.qty_change < 0 AND m.movement_type = 'distribution' THEN -m.qty_change ELSE 0 END) AS distributed,
SUM(CASE WHEN m.qty_change < 0 AND m.movement_type = 'out' THEN -m.qty_change ELSE 0 END) AS other_out,
SUM(CASE WHEN m.movement_type = 'adjust' THEN m.qty_change ELSE 0 END) AS adjust_net
")
->whereIn('m.item_id', $itemIds)
->groupBy('m.item_id');
if (!$isAllYears) {
$qb->where('m.school_year', $selectedYear);
}
foreach ($qb->get()->map(fn ($r) => (array) $r)->all() as $r) {
$aggById[(int) $r['item_id']] = [
'opening' => (int) ($r['opening'] ?? 0),
'received' => (int) ($r['received'] ?? 0),
'distributed' => (int) ($r['distributed'] ?? 0),
'other_out' => (int) ($r['other_out'] ?? 0),
'adjust_net' => (int) ($r['adjust_net'] ?? 0),
];
}
foreach ($items as $it) {
$id = (int) $it['id'];
$a = $aggById[$id] ?? ['opening' => 0, 'received' => 0, 'distributed' => 0, 'other_out' => 0, 'adjust_net' => 0];
$opening = (int) $a['opening'];
$received = (int) $a['received'];
$distributed = (int) $a['distributed'];
$otherOut = (int) $a['other_out'];
$adjustNet = (int) $a['adjust_net'];
$ending = $opening + $received - $distributed - $otherOut + $adjustNet;
$onhand = (int) ($it['quantity'] ?? 0);
$variance = $onhand - $ending;
$rows[] = [
'id' => $id,
'name' => (string) ($it['name'] ?? ''),
'type' => (string) ($it['type'] ?? ''),
'category' => (string) ($it['category_name'] ?? ''),
'opening' => $opening,
'received' => $received,
'distributed' => $distributed,
'other_out' => $otherOut,
'adjust_net' => $adjustNet,
'ending' => $ending,
'onhand' => $onhand,
'variance' => $variance,
];
$totals['opening'] += $opening;
$totals['received'] += $received;
$totals['distributed'] += $distributed;
$totals['other_out'] += $otherOut;
$totals['adjust_net'] += $adjustNet;
$totals['ending'] += $ending;
$totals['onhand'] += $onhand;
$totals['variance'] += $variance;
}
}
return [
'selectedYear' => $selectedYear,
'currentYear' => $this->context->schoolYear(),
'schoolYears' => $this->getSchoolYearsForType('book') ?: [$this->context->schoolYear(), 'All'],
'rows' => $rows,
'totals' => $totals,
'selectedType' => $selectedType,
];
}
private function getSchoolYearsForType(string $type): array
{
$years = InventoryItem::query()
->select('school_year')
->where('type', $type)
->whereNotNull('school_year')
->groupBy('school_year')
->orderByDesc('school_year')
->pluck('school_year')
->map(fn ($v) => trim((string) $v))
->filter()
->unique()
->values()
->all();
$currentYear = $this->context->schoolYear();
if ($currentYear && !in_array($currentYear, $years, true)) {
array_unshift($years, $currentYear);
}
return $years;
}
}