ScheduleBusinessRules.php 10 KB

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