| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- <?php
- namespace App\Services;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Support\Str;
- /**
- * Generic CRUD/clone helpers for the Pavão/Irrecusável pricing blocks, reused
- * across the 4 tables (base/unit level x Pavão/Irrecusável) since they all
- * share the same column shape (Matrícula, Aulas, Materiais, Valor Total).
- */
- class ProposalPricingService
- {
- private const VALUE_FIELDS = [
- 'registration_value',
- 'registration_included_in_course',
- 'registration_installments_allowed',
- 'registration_max_installments',
- 'classes_value',
- 'classes_included_in_course',
- 'classes_installments_allowed',
- 'classes_max_installments',
- 'classes_discount_percentage',
- 'materials_included_in_course',
- 'materials_installments_allowed',
- 'materials_max_installments',
- 'total_value',
- 'total_max_installments',
- ];
- /**
- * Create or update the pricing block owned by $ownerId (a class_package or
- * class_package_unit row), plus its material lines.
- */
- public function upsert(
- string $modelClass,
- string $productModelClass,
- string $ownerColumn,
- int $ownerId,
- array $data,
- bool $withCondition = false,
- ): Model {
- $materials = $data['materials'] ?? [];
- $attributes = array_intersect_key($data, array_flip($this->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,
- ]);
- });
- }
- 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);
- }
- }
|