fields($withCondition))); $attributes['materials_total_value'] = $this->calcMaterialValue($materials); /** @var Model $proposal */ $proposal = $modelClass::updateOrCreate([$ownerColumn => $ownerId], $attributes); $this->syncMaterials($proposal, $productModelClass, $materials); return $proposal->fresh('products.product'); } /** * Clone a base-level Pavão/Irrecusável proposal (with materials) into a * unit-level copy. Only called at replication time (new unit, or new * proposal replicated to existing units) — unit copies are independent * afterwards and are never overwritten by later base updates. */ public function cloneToUnit( ?Model $baseProposal, string $unitModelClass, string $unitProductModelClass, string $unitOwnerColumn, int $unitOwnerId, string $baseForeignKeyColumn, bool $withCondition = false, ): void { if (!$baseProposal) return; $attributes = $baseProposal->only($this->fields($withCondition)); $attributes['materials_total_value'] = $baseProposal->materials_total_value; $attributes[$unitOwnerColumn] = $unitOwnerId; $attributes[$baseForeignKeyColumn] = $baseProposal->id; /** @var Model $unitProposal */ $unitProposal = $unitModelClass::create($attributes); $baseProposal->products->each(function ($product) use ($unitProposal, $unitProductModelClass) { $unitProductModelClass::create([ $this->foreignKeyFor($unitProposal) => $unitProposal->id, 'product_id' => $product->product_id, 'quantity' => $product->quantity, 'price' => $product->price, ]); }); } /** * Remove the pricing block for $ownerId, if one exists — used when the * user turns a Pavão/Irrecusável section off. Only ever touches the * owner's own row (base or unit); never cascades to already-cloned copies. */ public function deleteFor(string $modelClass, string $ownerColumn, int $ownerId): void { $modelClass::where($ownerColumn, $ownerId)->delete(); } private function fields(bool $withCondition): array { return $withCondition ? [...self::VALUE_FIELDS, 'condition'] : self::VALUE_FIELDS; } private function syncMaterials(Model $proposal, string $productModelClass, array $materials): void { $proposal->products()->delete(); foreach ($materials as $material) { $productModelClass::create([ $this->foreignKeyFor($proposal) => $proposal->id, 'product_id' => $material['product_id'], 'quantity' => $material['quantity'], 'price' => $material['price'], ]); } } private function foreignKeyFor(Model $model): string { return Str::snake(class_basename($model)) . '_id'; } private function calcMaterialValue(array $materials): float { return array_reduce($materials, fn($carry, $m) => $carry + ($m['quantity'] * $m['price']), 0.0); } }