ClassPackageUnitService.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. namespace App\Services;
  3. use App\Models\ClassPackageUnit;
  4. use App\Models\ClassPackageUnitProduct;
  5. use App\Models\IrrecusableProposalUnit;
  6. use App\Models\IrrecusableProposalUnitProduct;
  7. use App\Models\PavaoProposalUnit;
  8. use App\Models\PavaoProposalUnitProduct;
  9. use Illuminate\Database\Eloquent\Collection;
  10. class ClassPackageUnitService
  11. {
  12. public function __construct(
  13. protected ProposalPricingService $pricingService,
  14. ) {}
  15. private const EAGER_LOAD = [
  16. 'products.product',
  17. 'pavaoProposalUnit.products.product',
  18. 'irrecusableProposalUnit.products.product',
  19. ];
  20. public function getByUnit(int $unitId): Collection
  21. {
  22. return ClassPackageUnit::with(self::EAGER_LOAD)
  23. ->where('unit_id', $unitId)
  24. ->where('visible', true)
  25. ->orderBy('name')
  26. ->get();
  27. }
  28. public function getAllByUnit(int $unitId): Collection
  29. {
  30. return ClassPackageUnit::with(self::EAGER_LOAD)
  31. ->where('unit_id', $unitId)
  32. ->orderBy('name')
  33. ->get();
  34. }
  35. public function findByIdForUnit(int $id, int $unitId): ClassPackageUnit
  36. {
  37. return ClassPackageUnit::with(self::EAGER_LOAD)
  38. ->where('unit_id', $unitId)
  39. ->findOrFail($id);
  40. }
  41. public function create(array $data): ClassPackageUnit
  42. {
  43. $materials = $data['materials'] ?? [];
  44. $pavao = $data['pavao'] ?? [];
  45. $irrecusavel = $data['irrecusavel'] ?? [];
  46. unset($data['materials'], $data['pavao'], $data['irrecusavel']);
  47. $data['contract_material_value'] = $this->calcMaterialValue($materials);
  48. $packageUnit = ClassPackageUnit::create($data);
  49. $this->syncProducts($packageUnit, $materials);
  50. $this->pricingService->upsert(PavaoProposalUnit::class, PavaoProposalUnitProduct::class, 'class_package_unit_id', $packageUnit->id, $pavao);
  51. $this->pricingService->upsert(IrrecusableProposalUnit::class, IrrecusableProposalUnitProduct::class, 'class_package_unit_id', $packageUnit->id, $irrecusavel, withCondition: true);
  52. return $packageUnit->load(self::EAGER_LOAD);
  53. }
  54. public function update(int $id, int $unitId, array $data): ClassPackageUnit
  55. {
  56. $packageUnit = $this->findByIdForUnit($id, $unitId);
  57. $materials = $data['materials'] ?? null;
  58. $pavao = $data['pavao'] ?? null;
  59. $irrecusavel = $data['irrecusavel'] ?? null;
  60. unset($data['materials'], $data['pavao'], $data['irrecusavel']);
  61. if ($materials !== null) {
  62. $data['contract_material_value'] = $this->calcMaterialValue($materials);
  63. }
  64. $packageUnit->update($data);
  65. if ($materials !== null) {
  66. $this->syncProducts($packageUnit, $materials);
  67. }
  68. // Edits here only affect this unit's own copy — never propagate up to the base package.
  69. if ($pavao !== null) {
  70. $this->pricingService->upsert(PavaoProposalUnit::class, PavaoProposalUnitProduct::class, 'class_package_unit_id', $packageUnit->id, $pavao);
  71. }
  72. if ($irrecusavel !== null) {
  73. $this->pricingService->upsert(IrrecusableProposalUnit::class, IrrecusableProposalUnitProduct::class, 'class_package_unit_id', $packageUnit->id, $irrecusavel, withCondition: true);
  74. }
  75. return $packageUnit->fresh(self::EAGER_LOAD);
  76. }
  77. public function toggleVisibility(int $id, int $unitId): ClassPackageUnit
  78. {
  79. $packageUnit = $this->findByIdForUnit($id, $unitId);
  80. $packageUnit->update(['visible' => !$packageUnit->visible]);
  81. return $packageUnit->fresh();
  82. }
  83. public function delete(int $id, int $unitId): bool
  84. {
  85. $packageUnit = $this->findByIdForUnit($id, $unitId);
  86. return $packageUnit->delete();
  87. }
  88. /**
  89. * Sync products on an existing ClassPackageUnit from the base package.
  90. */
  91. public function syncProductsFromBasePackage(ClassPackageUnit $packageUnit, \App\Models\ClassPackage $basePackage): void
  92. {
  93. $packageUnit->products()->delete();
  94. $basePackage->products->each(function ($product) use ($packageUnit) {
  95. ClassPackageUnitProduct::create([
  96. 'class_package_unit_id' => $packageUnit->id,
  97. 'product_id' => $product->id,
  98. 'quantity' => $product->pivot->quantity,
  99. 'price' => $product->pivot->price,
  100. ]);
  101. });
  102. }
  103. public function replicateFromBasePackage(int $unitId, \App\Models\ClassPackage $basePackage): ClassPackageUnit
  104. {
  105. $packageUnit = ClassPackageUnit::create([
  106. 'unit_id' => $unitId,
  107. 'class_package_id' => $basePackage->id,
  108. 'name' => $basePackage->name,
  109. 'quantity_classes' => $basePackage->quantity_classes,
  110. 'contract_value' => $basePackage->contract_value,
  111. 'contract_material_value' => $basePackage->contract_material_value,
  112. 'contract_register_value' => $basePackage->contract_register_value,
  113. 'contrat_discount_value' => $basePackage->contrat_discount_value,
  114. 'class_duration_minutes' => $basePackage->class_duration_minutes,
  115. 'weekday' => $basePackage->weekday,
  116. 'start_time' => $basePackage->start_time,
  117. 'second_weekday' => $basePackage->second_weekday,
  118. 'second_start_time' => $basePackage->second_start_time,
  119. 'visible' => true,
  120. ]);
  121. $basePackage->products->each(function ($product) use ($packageUnit) {
  122. ClassPackageUnitProduct::create([
  123. 'class_package_unit_id' => $packageUnit->id,
  124. 'product_id' => $product->id,
  125. 'quantity' => $product->pivot->quantity,
  126. 'price' => $product->pivot->price,
  127. ]);
  128. });
  129. $this->pricingService->cloneToUnit(
  130. $basePackage->pavaoProposal,
  131. PavaoProposalUnit::class,
  132. PavaoProposalUnitProduct::class,
  133. 'class_package_unit_id',
  134. $packageUnit->id,
  135. 'pavao_proposal_id',
  136. );
  137. $this->pricingService->cloneToUnit(
  138. $basePackage->irrecusableProposal,
  139. IrrecusableProposalUnit::class,
  140. IrrecusableProposalUnitProduct::class,
  141. 'class_package_unit_id',
  142. $packageUnit->id,
  143. 'irrecusable_proposal_id',
  144. withCondition: true,
  145. );
  146. return $packageUnit;
  147. }
  148. private function syncProducts(ClassPackageUnit $packageUnit, array $materials): void
  149. {
  150. $packageUnit->products()->delete();
  151. foreach ($materials as $m) {
  152. ClassPackageUnitProduct::create([
  153. 'class_package_unit_id' => $packageUnit->id,
  154. 'product_id' => $m['product_id'],
  155. 'quantity' => $m['quantity'],
  156. 'price' => $m['price'],
  157. ]);
  158. }
  159. }
  160. private function calcMaterialValue(array $materials): float
  161. {
  162. return array_reduce($materials, fn($carry, $m) => $carry + ($m['quantity'] * $m['price']), 0.0);
  163. }
  164. }