ClassPackageService.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. namespace App\Services;
  3. use App\Models\ClassPackage;
  4. use App\Models\ClassPackageUnit;
  5. use App\Models\IrrecusableProposal;
  6. use App\Models\IrrecusableProposalProduct;
  7. use App\Models\PavaoProposal;
  8. use App\Models\PavaoProposalProduct;
  9. use App\Models\Unit;
  10. use Illuminate\Database\Eloquent\Collection;
  11. use Illuminate\Support\Facades\DB;
  12. class ClassPackageService
  13. {
  14. private const EAGER_LOAD = [
  15. 'unitPackages',
  16. 'groups',
  17. 'pavaoProposal.products.product',
  18. 'irrecusableProposal.products.product',
  19. ];
  20. public function __construct(
  21. protected ClassPackageUnitService $unitService,
  22. protected ProposalPricingService $pricingService,
  23. ) {}
  24. public function getAll(): Collection
  25. {
  26. return ClassPackage::with(['pavaoProposal', 'irrecusableProposal'])->orderBy('created_at', 'desc')->get();
  27. }
  28. public function findById(int $id): ?ClassPackage
  29. {
  30. return ClassPackage::with(self::EAGER_LOAD)->find($id);
  31. }
  32. public function create(array $data): ClassPackage
  33. {
  34. $unitVisibilities = $data['unit_visibilities'] ?? null;
  35. $groupIds = $data['group_ids'] ?? [];
  36. $pavao = array_key_exists('pavao', $data) ? $data['pavao'] : null;
  37. $irrecusavel = array_key_exists('irrecusavel', $data) ? $data['irrecusavel'] : null;
  38. unset($data['unit_visibilities'], $data['group_ids'], $data['pavao'], $data['irrecusavel']);
  39. $package = ClassPackage::create($data);
  40. if ($pavao !== null) {
  41. $this->pricingService->upsert(PavaoProposal::class, PavaoProposalProduct::class, 'class_package_id', $package->id, $pavao);
  42. }
  43. if ($irrecusavel !== null) {
  44. $this->pricingService->upsert(IrrecusableProposal::class, IrrecusableProposalProduct::class, 'class_package_id', $package->id, $irrecusavel, withCondition: true);
  45. }
  46. // Replicate to all units (all start visible = true)
  47. $this->replicateToAllUnits($package->load(['pavaoProposal.products', 'irrecusableProposal.products']));
  48. // Sync selected groups
  49. if (!empty($groupIds)) {
  50. $package->groups()->sync($groupIds);
  51. }
  52. // Apply visibility overrides
  53. $this->applyVisibilityOverrides($package, $unitVisibilities, $groupIds);
  54. return $package->load(self::EAGER_LOAD);
  55. }
  56. public function update(int $id, array $data): ?ClassPackage
  57. {
  58. $package = $this->findById($id);
  59. if (!$package) return null;
  60. $unitVisibilities = $data['unit_visibilities'] ?? null;
  61. $groupIds = array_key_exists('group_ids', $data) ? ($data['group_ids'] ?? []) : null;
  62. $pavaoProvided = array_key_exists('pavao', $data);
  63. $irrecusavelProvided = array_key_exists('irrecusavel', $data);
  64. $pavao = $data['pavao'] ?? null;
  65. $irrecusavel = $data['irrecusavel'] ?? null;
  66. unset($data['unit_visibilities'], $data['group_ids'], $data['pavao'], $data['irrecusavel']);
  67. $package->update($data);
  68. $scheduleFields = array_intersect_key($data, array_flip([
  69. 'class_duration_minutes',
  70. 'weekday',
  71. 'start_time',
  72. 'second_weekday',
  73. 'second_start_time',
  74. ]));
  75. if (!empty($scheduleFields)) {
  76. ClassPackageUnit::where('class_package_id', $package->id)->update($scheduleFields);
  77. }
  78. // Pavão/Irrecusável updates never propagate to already-cloned unit copies.
  79. if ($pavaoProvided) {
  80. if ($pavao !== null) {
  81. $this->pricingService->upsert(PavaoProposal::class, PavaoProposalProduct::class, 'class_package_id', $package->id, $pavao);
  82. } else {
  83. $this->pricingService->deleteFor(PavaoProposal::class, 'class_package_id', $package->id);
  84. }
  85. }
  86. if ($irrecusavelProvided) {
  87. if ($irrecusavel !== null) {
  88. $this->pricingService->upsert(IrrecusableProposal::class, IrrecusableProposalProduct::class, 'class_package_id', $package->id, $irrecusavel, withCondition: true);
  89. } else {
  90. $this->pricingService->deleteFor(IrrecusableProposal::class, 'class_package_id', $package->id);
  91. }
  92. }
  93. if ($groupIds !== null) {
  94. $package->groups()->sync($groupIds);
  95. }
  96. // Determine current group IDs (after sync)
  97. $currentGroupIds = $package->groups()->pluck('groups.id')->toArray();
  98. if ($unitVisibilities !== null || $groupIds !== null) {
  99. $this->applyVisibilityOverrides($package, $unitVisibilities, $currentGroupIds);
  100. }
  101. return $package->fresh(self::EAGER_LOAD);
  102. }
  103. public function delete(int $id): bool
  104. {
  105. $package = ClassPackage::find($id);
  106. if (!$package) return false;
  107. return $package->delete();
  108. }
  109. public function replicateToAllUnits(ClassPackage $package): void
  110. {
  111. Unit::all()->each(function (Unit $unit) use ($package) {
  112. // Skip if ClassPackageUnit already exists for this package/unit combo
  113. $exists = ClassPackageUnit::withTrashed()
  114. ->where('class_package_id', $package->id)
  115. ->where('unit_id', $unit->id)
  116. ->exists();
  117. if (!$exists) {
  118. $this->unitService->replicateFromBasePackage($unit->id, $package);
  119. }
  120. });
  121. }
  122. /**
  123. * Apply visibility overrides:
  124. * - Units in selected groups → forced visible = true
  125. * - Other units → use explicit unit_visibilities (if provided)
  126. */
  127. private function applyVisibilityOverrides(ClassPackage $package, ?array $unitVisibilities, array $groupIds): void
  128. {
  129. // Get all unit IDs that belong to the selected groups
  130. $groupUnitIds = [];
  131. if (!empty($groupIds)) {
  132. $groupUnitIds = DB::table('group_units')
  133. ->whereIn('group_id', $groupIds)
  134. ->pluck('unit_id')
  135. ->unique()
  136. ->toArray();
  137. }
  138. // Force group units to visible = true
  139. if (!empty($groupUnitIds)) {
  140. ClassPackageUnit::where('class_package_id', $package->id)
  141. ->whereIn('unit_id', $groupUnitIds)
  142. ->update(['visible' => true]);
  143. }
  144. // Apply explicit visibility for non-group units
  145. if (!empty($unitVisibilities)) {
  146. foreach ($unitVisibilities as $uv) {
  147. if (in_array($uv['unit_id'], $groupUnitIds)) {
  148. continue; // group units cannot be hidden
  149. }
  150. ClassPackageUnit::where('class_package_id', $package->id)
  151. ->where('unit_id', $uv['unit_id'])
  152. ->update(['visible' => $uv['visible']]);
  153. }
  154. }
  155. }
  156. }