Преглед изворни кода

feat: adiciona logica para criar/editar pacote

ebagabee пре 1 месец
родитељ
комит
e60d60d6a2

+ 6 - 0
app/Http/Controllers/ProductController.php

@@ -13,6 +13,12 @@ public function __construct(
         protected ProductService $service,
     ) {}
 
+    public function select(): JsonResponse
+    {
+        $items = $this->service->getAllForSelect();
+        return $this->successResponse(payload: $items);
+    }
+
     public function index(): JsonResponse
     {
         $items = $this->service->getAll();

+ 8 - 4
app/Http/Requests/ClassPackageRequest.php

@@ -14,13 +14,17 @@ public function rules(): array
             'name'                    => "$required|string|max:255",
             'quantity_classes'        => "$required|integer|min:1",
             'contract_value'          => "$required|numeric|min:0",
-            'contract_material_value' => "$required|numeric|min:0",
             'contract_register_value' => "$required|numeric|min:0",
             'contrat_discount_value'  => 'nullable|numeric|min:0',
 
-            'unit_visibilities'              => 'nullable|array',
-            'unit_visibilities.*.unit_id'   => 'required|integer|exists:units,id',
-            'unit_visibilities.*.visible'   => 'required|boolean',
+            'unit_visibilities'            => 'nullable|array',
+            'unit_visibilities.*.unit_id'  => 'required|integer|exists:units,id',
+            'unit_visibilities.*.visible'  => 'required|boolean',
+
+            'materials'                => 'nullable|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',
         ];
     }
 }

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

@@ -29,6 +29,15 @@ public function toArray(Request $request): array
                     'visible' => (bool) $unit->pivot->visible,
                 ])
             ),
+
+            'materials' => $this->whenLoaded('products', fn() =>
+                $this->products->map(fn($product) => [
+                    'product_id' => $product->id,
+                    'name'       => $product->name,
+                    'quantity'   => $product->pivot->quantity,
+                    'price'      => (float) $product->pivot->price,
+                ])
+            ),
         ];
     }
 

+ 9 - 1
app/Models/ClassPackage.php

@@ -4,6 +4,7 @@
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 use Illuminate\Database\Eloquent\SoftDeletes;
 
 /**
@@ -35,10 +36,17 @@ class ClassPackage extends Model
         'updated_at'              => 'datetime',
     ];
 
-    public function units()
+    public function units(): BelongsToMany
     {
         return $this->belongsToMany(Unit::class, 'class_package_units')
             ->withPivot('visible')
             ->withTimestamps();
     }
+
+    public function products(): BelongsToMany
+    {
+        return $this->belongsToMany(Product::class, 'class_package_products')
+            ->withPivot(['quantity', 'price'])
+            ->withTimestamps();
+    }
 }

+ 36 - 6
app/Services/ClassPackageService.php

@@ -15,19 +15,23 @@ public function getAll(): Collection
 
     public function findById(int $id): ?ClassPackage
     {
-        return ClassPackage::with('units')->find($id);
+        return ClassPackage::with(['units', 'products'])->find($id);
     }
 
     public function create(array $data): ClassPackage
     {
         $unitVisibilities = $data['unit_visibilities'] ?? [];
-        unset($data['unit_visibilities']);
+        $materials        = $data['materials'] ?? [];
+        unset($data['unit_visibilities'], $data['materials']);
+
+        $data['contract_material_value'] = $this->calcMaterialValue($materials);
 
         $package = ClassPackage::create($data);
 
         $this->syncUnitVisibilities($package, $unitVisibilities);
+        $this->syncMaterials($package, $materials);
 
-        return $package->load('units');
+        return $package->load(['units', 'products']);
     }
 
     public function update(int $id, array $data): ?ClassPackage
@@ -36,7 +40,12 @@ public function update(int $id, array $data): ?ClassPackage
         if (!$package) return null;
 
         $unitVisibilities = $data['unit_visibilities'] ?? null;
-        unset($data['unit_visibilities']);
+        $materials        = $data['materials'] ?? null;
+        unset($data['unit_visibilities'], $data['materials']);
+
+        if ($materials !== null) {
+            $data['contract_material_value'] = $this->calcMaterialValue($materials);
+        }
 
         $package->update($data);
 
@@ -44,7 +53,11 @@ public function update(int $id, array $data): ?ClassPackage
             $this->syncUnitVisibilities($package, $unitVisibilities);
         }
 
-        return $package->fresh('units');
+        if ($materials !== null) {
+            $this->syncMaterials($package, $materials);
+        }
+
+        return $package->fresh(['units', 'products']);
     }
 
     public function delete(int $id): bool
@@ -58,7 +71,7 @@ public function delete(int $id): bool
     public function getAllUnitsWithVisibility(int $packageId): \Illuminate\Support\Collection
     {
         $allUnits = Unit::orderBy('fantasy_name')->get(['id', 'fantasy_name']);
-        $package = ClassPackage::with('units')->find($packageId);
+        $package  = ClassPackage::with('units')->find($packageId);
 
         $visibilityMap = $package
             ? $package->units->keyBy('id')
@@ -81,4 +94,21 @@ private function syncUnitVisibilities(ClassPackage $package, array $unitVisibili
         }
         $package->units()->sync($syncData);
     }
+
+    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);
+    }
 }

+ 6 - 2
app/Services/ProductService.php

@@ -9,8 +9,12 @@ class ProductService
 {
     public function getAll(): Collection
     {
-        return Product::orderBy('created_at', 'desc')
-            ->get();
+        return Product::orderBy('created_at', 'desc')->get();
+    }
+
+    public function getAllForSelect(): Collection
+    {
+        return Product::orderBy('name')->get(['id', 'name', 'price_sale']);
     }
 
     public function findById(int $id): ?Product

+ 27 - 0
database/migrations/2026_05_05_000001_create_class_package_products_table.php

@@ -0,0 +1,27 @@
+<?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_products', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('class_package_id')->constrained('class_packages')->cascadeOnDelete();
+            $table->foreignId('product_id')->constrained('products')->cascadeOnDelete();
+            $table->integer('quantity')->default(1);
+            $table->decimal('price', 10, 2);
+            $table->timestamps();
+
+            $table->unique(['class_package_id', 'product_id']);
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('class_package_products');
+    }
+};

+ 1 - 0
database/seeders/DatabaseSeeder.php

@@ -17,6 +17,7 @@ public function run(): void
             UserTypePermissionSeeder::class,
             BrazilCitiesSeeder::class,
             ClassPackageSeeder::class,
+            ProductSeeder::class,
         ]);
     }
 }

+ 149 - 0
database/seeders/ProductSeeder.php

@@ -0,0 +1,149 @@
+<?php
+
+namespace Database\Seeders;
+
+use App\Models\Product;
+use Illuminate\Database\Seeder;
+
+class ProductSeeder extends Seeder
+{
+    public function run(): void
+    {
+        $products = [
+            [
+                'name'               => 'Apostila Nível 1',
+                'sku'                => 'AP-NV1',
+                'barcode'            => '7890001000001',
+                'price_cost'         => 18.00,
+                'price_sale'         => 35.00,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.300,
+                'length'             => 29.7,
+                'height'             => 21.0,
+            ],
+            [
+                'name'               => 'Apostila Nível 2',
+                'sku'                => 'AP-NV2',
+                'barcode'            => '7890001000002',
+                'price_cost'         => 18.00,
+                'price_sale'         => 35.00,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.300,
+                'length'             => 29.7,
+                'height'             => 21.0,
+            ],
+            [
+                'name'               => 'Apostila Nível 3',
+                'sku'                => 'AP-NV3',
+                'barcode'            => '7890001000003',
+                'price_cost'         => 18.00,
+                'price_sale'         => 35.00,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.300,
+                'length'             => 29.7,
+                'height'             => 21.0,
+            ],
+            [
+                'name'               => 'Kit Material Didático Básico',
+                'sku'                => 'KIT-MAT-BAS',
+                'barcode'            => '7890001000010',
+                'price_cost'         => 45.00,
+                'price_sale'         => 89.90,
+                'measure_unit'       => 'KIT',
+                'kit'                => true,
+                'visible_franchisee' => true,
+                'weight'             => 0.800,
+                'length'             => 30.0,
+                'height'             => 22.0,
+            ],
+            [
+                'name'               => 'Kit Material Didático Completo',
+                'sku'                => 'KIT-MAT-COMP',
+                'barcode'            => '7890001000011',
+                'price_cost'         => 90.00,
+                'price_sale'         => 169.90,
+                'measure_unit'       => 'KIT',
+                'kit'                => true,
+                'visible_franchisee' => true,
+                'weight'             => 1.500,
+                'length'             => 30.0,
+                'height'             => 22.0,
+            ],
+            [
+                'name'               => 'Caderno de Atividades',
+                'sku'                => 'CAD-ATI',
+                'barcode'            => '7890001000020',
+                'price_cost'         => 12.00,
+                'price_sale'         => 24.90,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.200,
+                'length'             => 29.7,
+                'height'             => 21.0,
+            ],
+            [
+                'name'               => 'Livro do Aluno — Ginástica do Cérebro',
+                'sku'                => 'LV-ALUNO-GC',
+                'barcode'            => '7890001000030',
+                'price_cost'         => 32.00,
+                'price_sale'         => 59.90,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.450,
+                'length'             => 23.0,
+                'height'             => 16.0,
+            ],
+            [
+                'name'               => 'Camiseta Ginástica do Cérebro',
+                'sku'                => 'CAM-GC',
+                'barcode'            => '7890001000040',
+                'price_cost'         => 20.00,
+                'price_sale'         => 49.90,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.200,
+                'length'             => 30.0,
+                'height'             => 20.0,
+            ],
+            [
+                'name'               => 'Pasta do Aluno',
+                'sku'                => 'PAST-ALUNO',
+                'barcode'            => '7890001000050',
+                'price_cost'         => 5.00,
+                'price_sale'         => 12.90,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => true,
+                'weight'             => 0.100,
+                'length'             => 33.0,
+                'height'             => 23.0,
+            ],
+            [
+                'name'               => 'Certificado de Conclusão',
+                'sku'                => 'CERT-CONCL',
+                'barcode'            => '7890001000060',
+                'price_cost'         => 2.00,
+                'price_sale'         => 5.00,
+                'measure_unit'       => 'UN',
+                'kit'                => false,
+                'visible_franchisee' => false,
+                'weight'             => 0.050,
+                'length'             => 29.7,
+                'height'             => 21.0,
+            ],
+        ];
+
+        foreach ($products as $product) {
+            Product::firstOrCreate(['sku' => $product['sku']], $product);
+        }
+    }
+}

+ 2 - 0
routes/authRoutes/product.php

@@ -4,6 +4,8 @@
 use App\Http\Controllers\ProductController;
 
 Route::controller(ProductController::class)->prefix('product')->group(function () {
+    Route::get('/all/select', 'select');
+
     Route::get('/', 'index')->middleware('permission:product,view');
 
     Route::post('/', 'store')->middleware('permission:product,add');