ScheduleBusinessRules.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. namespace App\Rules;
  3. use App\Models\Provider;
  4. use App\Models\ProviderBlockedDay;
  5. use App\Models\ProviderWorkingDay;
  6. use App\Models\Schedule;
  7. use App\Models\ScheduleProposal;
  8. use Carbon\Carbon;
  9. use Illuminate\Support\Facades\Log;
  10. class ScheduleBusinessRules
  11. {
  12. // Status que devem ser ignorados na validação de limite por semana
  13. private const EXCLUDED_STATUSES = ['cancelled', 'rejected'];
  14. /**
  15. * Valida se o prestador pode ter mais um agendamento com o cliente na semana
  16. * Limite: 2 agendamentos por semana (domingo a sábado)
  17. * @param int $clientId
  18. * @param int $providerId
  19. * @param string $date (Y-m-d)
  20. * @param int|null $excludeScheduleId
  21. * @return bool
  22. * @throws \Exception
  23. */
  24. public static function validateWeeklyScheduleLimit($clientId, $providerId, $date, $excludeScheduleId = null)
  25. {
  26. $date = Carbon::parse($date);
  27. $weekStart = $date->copy()->startOfWeek(Carbon::SUNDAY);
  28. $weekEnd = $date->copy()->endOfWeek(Carbon::SATURDAY);
  29. $weeklySchedulesCount = Schedule::where('client_id', $clientId)
  30. ->where('provider_id', $providerId)
  31. ->whereNotIn('status', self::EXCLUDED_STATUSES)
  32. ->whereBetween('date', [$weekStart->format('Y-m-d'), $weekEnd->format('Y-m-d')])
  33. ->when($excludeScheduleId, function ($query) use ($excludeScheduleId) {
  34. $query->where('id', '!=', $excludeScheduleId);
  35. })
  36. ->count();
  37. if ($weeklySchedulesCount >= 2) {
  38. throw new \Exception(__('validation.custom.schedule.weekly_limit_exceeded'));
  39. }
  40. return true;
  41. }
  42. /**
  43. * Valida se o prestador tem horário de trabalho cadastrado para o dia da semana e período
  44. * @param int $provider_id
  45. * @param int $day_of_week (0 - domingo, 6 - sábado)
  46. * @param string $period ('morning' ou 'afternoon')
  47. * @return bool
  48. * @throws \Exception
  49. */
  50. public static function validateWorkingDay($provider_id, $day_of_week, $period)
  51. {
  52. $workingDay = ProviderWorkingDay::where('provider_id', $provider_id)
  53. ->where('day', $day_of_week)
  54. ->where('period', $period)
  55. ->first();
  56. if(!$workingDay) {
  57. throw new \Exception(__('validation.custom.schedule.provider_not_working'));
  58. }
  59. return true;
  60. }
  61. /**
  62. * Valida se o prestador tem bloqueio cadastrado para o dia e horário
  63. * @param int $provider_id
  64. * @param string $date_ymd (Y-m-d)
  65. * @param string $start_time (H:i:s)
  66. * @param string $end_time (H:i:s)
  67. * @return bool
  68. * @throws \Exception
  69. */
  70. public static function validateBlockedDay($provider_id, $date_ymd, $start_time, $end_time)
  71. {
  72. $blockedDay = ProviderBlockedDay::where('provider_id', $provider_id)
  73. ->where('date', $date_ymd)
  74. ->where(function ($query) use ($start_time, $end_time) {
  75. $query->where('period', 'full')
  76. ->orWhere(function ($q) use ($start_time, $end_time) {
  77. $q->where('period', 'partial')
  78. ->where(function ($q2) use ($start_time, $end_time) {
  79. $q2->whereBetween('init_hour', [$start_time, $end_time])
  80. ->orWhereBetween('end_hour', [$start_time, $end_time])
  81. ->orWhere(function ($q3) use ($start_time, $end_time) {
  82. $q3->where('init_hour', '<=', $start_time)
  83. ->where('end_hour', '>=', $end_time);
  84. });
  85. });
  86. });
  87. })
  88. ->first();
  89. if ($blockedDay) {
  90. throw new \Exception(__('validation.custom.schedule.provider_blocked'));
  91. }
  92. return true;
  93. }
  94. public static function validatePricePeriod($provider_id, $min_price, $max_price, $period_type)
  95. {
  96. if ($min_price < 0 || $max_price < 0) {
  97. throw new \Exception(__('validation.custom.schedule.invalid_price'));
  98. }
  99. if ($min_price > $max_price) {
  100. throw new \Exception(__('validation.custom.schedule.invalid_price_range'));
  101. }
  102. $provider = Provider::find($provider_id);
  103. $provider_price_period = 0;
  104. switch ($period_type):
  105. case '2': //2 horas
  106. $provider_price_period = $provider->daily_price_2h;
  107. break;
  108. case '4': //4 horas
  109. $provider_price_period = $provider->daily_price_4h;
  110. break;
  111. case '6': //6 horas
  112. $provider_price_period = $provider->daily_price_6h;
  113. break;
  114. case '8': //8 horas
  115. $provider_price_period = $provider->daily_price_8h;
  116. break;
  117. default:
  118. throw new \Exception(__('validation.custom.schedule.invalid_period_type'));
  119. endswitch;
  120. if ($provider_price_period < $min_price || $provider_price_period > $max_price) {
  121. throw new \Exception(__('validation.custom.schedule.price_not_in_range'));
  122. }
  123. return true;
  124. }
  125. /**
  126. * Valida se o prestador tem outro agendamento no mesmo dia e horário
  127. * @param int $provider_id
  128. * @param string $date_ymd (Y-m-d)
  129. * @param string $start_time (H:i:s)
  130. * @param string $end_time (H:i:s)
  131. * @param int|null $exclude_schedule_id (id do agendamento a ser excluído da validação, usado para edição de agendamento)
  132. * @return bool
  133. * @throws \Exception
  134. */
  135. public static function validateConflictingSchedule($provider_id, $date_ymd, $start_time, $end_time, $exclude_schedule_id = null)
  136. {
  137. $conflictingSchedule = Schedule::where('provider_id', $provider_id)
  138. ->where('date', $date_ymd)
  139. ->whereIn('status', ['pending', 'accepted', 'paid', 'started'])
  140. ->where(function ($query) use ($start_time, $end_time) {
  141. $query->whereBetween('start_time', [$start_time, $end_time])
  142. ->orWhereBetween('end_time', [$start_time, $end_time])
  143. ->orWhere(function ($q) use ($start_time, $end_time) {
  144. $q->where('start_time', '<=', $start_time)
  145. ->where('end_time', '>=', $end_time);
  146. });
  147. })
  148. ->when($exclude_schedule_id, function ($query) use ($exclude_schedule_id) {
  149. $query->where('id', '!=', $exclude_schedule_id);
  150. })
  151. ->first();
  152. if ($conflictingSchedule) {
  153. throw new \Exception(__('validation.custom.schedule.provider_conflicting_schedule'));
  154. }
  155. return true;
  156. }
  157. /**
  158. * Valida se o prestador tem outro agendamento com o mesmo cliente no mesmo dia e horário
  159. * @param int $provider_id
  160. * @param string $date_ymd (Y-m-d)
  161. * @param string $start_time (H:i:s)
  162. * @param string $end_time (H:i:s)
  163. * @param int|null $exclude_schedule_id (id do agendamento a ser excluído da validação, usado para edição de agendamento)
  164. * @return bool
  165. * @throws \Exception
  166. */
  167. public static function validateConflictingSameProposal($provider_id, $schedule_id)
  168. {
  169. $conflictingSameProposal = ScheduleProposal::where('schedule_proposals.provider_id', $provider_id)
  170. ->where('schedule_proposals.schedule_id', $schedule_id)
  171. ->leftJoin('schedules', 'schedule_proposals.schedule_id', '=', 'schedules.id')
  172. ->whereNotIn('schedules.status', self::EXCLUDED_STATUSES)
  173. ->first();
  174. if ($conflictingSameProposal) {
  175. throw new \Exception(__('validation.custom.schedule.provider_conflicting_same_proposal'));
  176. }
  177. return true;
  178. }
  179. /**
  180. * Valida se o prestador tem outro agendamento com o mesmo cliente no mesmo dia e horário, ignorando o horário
  181. * @param int $provider_id
  182. * @param string $date_ymd (Y-m-d)
  183. * @param string $start_time (H:i:s)
  184. * @param string $end_time (H:i:s)
  185. * @param int|null $exclude_schedule_id (id do agendamento a ser excluído da validação, usado para edição de agendamento)
  186. * @return bool
  187. * @throws \Exception
  188. */
  189. public static function validateConflictingProposalSameDate($provider_id, $date_ymd, $start_time, $end_time, $exclude_schedule_id = null)
  190. {
  191. $conflictingProposalSameDate = ScheduleProposal::where('schedule_proposals.provider_id', $provider_id)
  192. ->leftJoin('schedules', 'schedule_proposals.schedule_id', '=', 'schedules.id')
  193. ->where('schedules.date', $date_ymd)
  194. ->where(function ($query) use ($start_time, $end_time) {
  195. $query->whereBetween('schedules.start_time', [$start_time, $end_time])
  196. ->orWhereBetween('schedules.end_time', [$start_time, $end_time])
  197. ->orWhere(function ($q) use ($start_time, $end_time) {
  198. $q->where('schedules.start_time', '<=', $start_time)
  199. ->where('schedules.end_time', '>=', $end_time);
  200. });
  201. })
  202. ->whereNotIn('status', self::EXCLUDED_STATUSES)
  203. ->when($exclude_schedule_id, function ($query) use ($exclude_schedule_id) {
  204. $query->where('schedules.id', '!=', $exclude_schedule_id);
  205. })
  206. ->first();
  207. if ($conflictingProposalSameDate) {
  208. throw new \Exception(__('validation.custom.schedule.provider_conflicting_proposal_same_date'));
  209. }
  210. return true;
  211. }
  212. }