Bläddra i källkod

feat(class_package): adiciona grupos

ebagabee 3 veckor sedan
förälder
incheckning
77aae87f0e

+ 7 - 0
app/Http/Requests/ClassPackageRequest.php

@@ -21,6 +21,13 @@ public function rules(): array
             'materials.*.product_id'   => 'required_with:materials|integer|exists:products,id',
             'materials.*.quantity'     => 'required_with:materials|integer|min:1',
             'materials.*.price'        => 'required_with:materials|numeric|min:0',
+
+            'group_ids'                        => 'nullable|array',
+            'group_ids.*'                      => 'integer|exists:groups,id',
+
+            'unit_visibilities'                => 'nullable|array',
+            'unit_visibilities.*.unit_id'      => 'required_with:unit_visibilities|integer|exists:units,id',
+            'unit_visibilities.*.visible'      => 'required_with:unit_visibilities|boolean',
         ];
     }
 }

+ 11 - 0
app/Http/Resources/ClassPackageResource.php

@@ -31,6 +31,17 @@ public function toArray(Request $request): array
                     'price'      => (float) $product->pivot->price,
                 ])
             ),
+
+            'unit_visibilities' => $this->whenLoaded('unitPackages', fn() =>
+                $this->unitPackages->map(fn($up) => [
+                    'unit_id' => $up->unit_id,
+                    'visible' => (bool) $up->visible,
+                ])
+            ),
+
+            'group_ids' => $this->whenLoaded('groups', fn() =>
+                $this->groups->pluck('id')->values()
+            ),
         ];
     }
 

+ 6 - 0
app/Models/ClassPackage.php

@@ -6,6 +6,7 @@
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 use Illuminate\Database\Eloquent\Relations\HasMany;
+use App\Models\Group;
 use Illuminate\Database\Eloquent\SoftDeletes;
 
 /**
@@ -70,4 +71,9 @@ public function products(): BelongsToMany
             ->withPivot(['quantity', 'price'])
             ->withTimestamps();
     }
+
+    public function groups(): BelongsToMany
+    {
+        return $this->belongsToMany(Group::class, 'class_package_groups');
+    }
 }

+ 92 - 8
app/Services/ClassPackageService.php

@@ -3,8 +3,10 @@
 namespace App\Services;
 
 use App\Models\ClassPackage;
+use App\Models\ClassPackageUnit;
 use App\Models\Unit;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Support\Facades\DB;
 
 class ClassPackageService
 {
@@ -19,13 +21,15 @@ public function getAll(): Collection
 
     public function findById(int $id): ?ClassPackage
     {
-        return ClassPackage::with('products')->find($id);
+        return ClassPackage::with(['products', 'unitPackages', 'groups'])->find($id);
     }
 
     public function create(array $data): ClassPackage
     {
-        $materials = $data['materials'] ?? [];
-        unset($data['materials']);
+        $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);
 
@@ -33,9 +37,18 @@ public function create(array $data): ClassPackage
 
         $this->syncMaterials($package, $materials);
 
+        // Replicate to all units (all start visible = true)
         $this->replicateToAllUnits($package->load('products'));
 
-        return $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
@@ -43,8 +56,10 @@ public function update(int $id, array $data): ?ClassPackage
         $package = $this->findById($id);
         if (!$package) return null;
 
-        $materials = $data['materials'] ?? null;
-        unset($data['materials']);
+        $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);
@@ -54,9 +69,22 @@ public function update(int $id, array $data): ?ClassPackage
 
         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);
         }
 
-        return $package->fresh('products');
+        // 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
@@ -70,7 +98,63 @@ public function delete(int $id): bool
     public function replicateToAllUnits(ClassPackage $package): void
     {
         Unit::all()->each(function (Unit $unit) use ($package) {
-            $this->unitService->replicateFromBasePackage($unit->id, $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);
         });
     }
 

+ 17 - 0
app/Services/ClassPackageUnitService.php

@@ -83,6 +83,23 @@ public function delete(int $id): bool
         return $packageUnit->delete();
     }
 
+    /**
+     * Sync products on an existing ClassPackageUnit from the base package.
+     */
+    public function syncProductsFromBasePackage(ClassPackageUnit $packageUnit, \App\Models\ClassPackage $basePackage): void
+    {
+        $packageUnit->products()->delete();
+
+        $basePackage->products->each(function ($product) use ($packageUnit) {
+            ClassPackageUnitProduct::create([
+                'class_package_unit_id' => $packageUnit->id,
+                'product_id'            => $product->id,
+                'quantity'              => $product->pivot->quantity,
+                'price'                 => $product->pivot->price,
+            ]);
+        });
+    }
+
     public function replicateFromBasePackage(int $unitId, \App\Models\ClassPackage $basePackage): ClassPackageUnit
     {
         $packageUnit = ClassPackageUnit::create([

+ 25 - 0
database/migrations/2026_05_20_000001_create_class_package_groups_table.php

@@ -0,0 +1,25 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('class_package_groups', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('class_package_id')->constrained('class_packages')->cascadeOnDelete();
+            $table->foreignId('group_id')->constrained('groups')->cascadeOnDelete();
+            $table->timestamps();
+
+            $table->unique(['class_package_id', 'group_id']);
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('class_package_groups');
+    }
+};