orderBy('created_at', 'desc')->get(); } public function findById(int $id): ?ClassPackage { return ClassPackage::with(['products', 'unitPackages', 'groups'])->find($id); } public function create(array $data): ClassPackage { $materials = $data['materials'] ?? []; $unitVisibilities = $data['unit_visibilities'] ?? null; $groupIds = $data['group_ids'] ?? []; unset($data['materials'], $data['unit_visibilities'], $data['group_ids']); $data['contract_material_value'] = $this->calcMaterialValue($materials); $package = ClassPackage::create($data); $this->syncMaterials($package, $materials); // Replicate to all units (all start visible = true) $this->replicateToAllUnits($package->load('products')); // Sync selected groups if (!empty($groupIds)) { $package->groups()->sync($groupIds); } // Apply visibility overrides $this->applyVisibilityOverrides($package, $unitVisibilities, $groupIds); return $package->load(['products', 'unitPackages', 'groups']); } public function update(int $id, array $data): ?ClassPackage { $package = $this->findById($id); if (!$package) return null; $materials = $data['materials'] ?? null; $unitVisibilities = $data['unit_visibilities'] ?? null; $groupIds = array_key_exists('group_ids', $data) ? ($data['group_ids'] ?? []) : null; unset($data['materials'], $data['unit_visibilities'], $data['group_ids']); if ($materials !== null) { $data['contract_material_value'] = $this->calcMaterialValue($materials); } $package->update($data); if ($materials !== null) { $this->syncMaterials($package, $materials); // Propagate material changes to unit packages $this->syncUnitPackageProducts($package->load('products')); } if ($groupIds !== null) { $package->groups()->sync($groupIds); } // Determine current group IDs (after sync) $currentGroupIds = $package->groups()->pluck('groups.id')->toArray(); if ($unitVisibilities !== null || $groupIds !== null) { $this->applyVisibilityOverrides($package, $unitVisibilities, $currentGroupIds); } return $package->fresh(['products', 'unitPackages', 'groups']); } public function delete(int $id): bool { $package = ClassPackage::find($id); if (!$package) return false; return $package->delete(); } public function replicateToAllUnits(ClassPackage $package): void { Unit::all()->each(function (Unit $unit) use ($package) { // Skip if ClassPackageUnit already exists for this package/unit combo $exists = ClassPackageUnit::withTrashed() ->where('class_package_id', $package->id) ->where('unit_id', $unit->id) ->exists(); if (!$exists) { $this->unitService->replicateFromBasePackage($unit->id, $package); } }); } /** * Apply visibility overrides: * - Units in selected groups → forced visible = true * - Other units → use explicit unit_visibilities (if provided) */ private function applyVisibilityOverrides(ClassPackage $package, ?array $unitVisibilities, array $groupIds): void { // Get all unit IDs that belong to the selected groups $groupUnitIds = []; if (!empty($groupIds)) { $groupUnitIds = DB::table('group_units') ->whereIn('group_id', $groupIds) ->pluck('unit_id') ->unique() ->toArray(); } // Force group units to visible = true if (!empty($groupUnitIds)) { ClassPackageUnit::where('class_package_id', $package->id) ->whereIn('unit_id', $groupUnitIds) ->update(['visible' => true]); } // Apply explicit visibility for non-group units if (!empty($unitVisibilities)) { foreach ($unitVisibilities as $uv) { if (in_array($uv['unit_id'], $groupUnitIds)) { continue; // group units cannot be hidden } ClassPackageUnit::where('class_package_id', $package->id) ->where('unit_id', $uv['unit_id']) ->update(['visible' => $uv['visible']]); } } } /** * When materials are updated, propagate product changes to all ClassPackageUnit records. */ private function syncUnitPackageProducts(ClassPackage $package): void { ClassPackageUnit::where('class_package_id', $package->id)->each(function (ClassPackageUnit $up) use ($package) { $this->unitService->syncProductsFromBasePackage($up, $package); }); } private function syncMaterials(ClassPackage $package, array $materials): void { $syncData = []; foreach ($materials as $m) { $syncData[$m['product_id']] = [ 'quantity' => $m['quantity'], 'price' => $m['price'], ]; } $package->products()->sync($syncData); } private function calcMaterialValue(array $materials): float { return array_reduce($materials, fn($carry, $m) => $carry + ($m['quantity'] * $m['price']), 0.0); } }