orderBy('id', 'desc') ->get(); return $custom_schedules; } public function getById($id) { return CustomSchedule::with([ 'schedule.client.user', 'schedule.address', 'serviceType', 'specialities.speciality' ])->findOrFail($id); } public function create(array $data) { DB::beginTransaction(); try { $quantity = $data['quantity'] ?? 1; $specialityIds = $data['speciality_ids'] ?? []; $createdCustomSchedules = []; for ($i = 0; $i < $quantity; $i++) { $scheduleData = [ 'client_id' => $data['client_id'], 'provider_id' => null, 'address_id' => $data['address_id'], 'date' => $data['date'], 'period_type' => $data['period_type'], 'schedule_type' => 'custom', 'start_time' => $data['start_time'], 'end_time' => $data['end_time'], 'status' => 'pending', 'total_amount' => 0, 'code' => str_pad(random_int(0, 9999), 4, '0', STR_PAD_LEFT), 'code_verified' => false, ]; $schedule = Schedule::create($scheduleData); $customScheduleData = [ 'schedule_id' => $schedule->id, 'address_type' => $data['address_type'], 'service_type_id' => $data['service_type_id'], 'description' => $data['description'] ?? null, 'min_price' => $data['min_price'], 'max_price' => $data['max_price'], 'offers_meal' => $data['offers_meal'] ?? false, ]; $customSchedule = CustomSchedule::create($customScheduleData); if (!empty($specialityIds)) { foreach ($specialityIds as $specialityId) { CustomScheduleSpeciality::create([ 'custom_schedule_id' => $customSchedule->id, 'speciality_id' => $specialityId, ]); } } $createdCustomSchedules[] = $customSchedule->load([ 'schedule.client.user', 'schedule.address', 'serviceType', 'specialities.speciality' ]); } DB::commit(); return $createdCustomSchedules; } catch (\Exception $e) { DB::rollBack(); Log::error('Error creating custom schedule: ' . $e->getMessage()); throw $e; } } public function update($id, array $data) { DB::beginTransaction(); try { $customSchedule = CustomSchedule::findOrFail($id); $schedule = $customSchedule->schedule; $scheduleUpdateData = []; if (isset($data['address_id'])) { $scheduleUpdateData['address_id'] = $data['address_id']; } if (isset($data['date'])) { $scheduleUpdateData['date'] = $data['date']; } if (isset($data['period_type'])) { $scheduleUpdateData['period_type'] = $data['period_type']; } if (isset($data['start_time'])) { $scheduleUpdateData['start_time'] = $data['start_time']; } if (isset($data['end_time'])) { $scheduleUpdateData['end_time'] = $data['end_time']; } if (!empty($scheduleUpdateData)) { $schedule->update($scheduleUpdateData); } $customScheduleUpdateData = []; if (isset($data['address_type'])) { $customScheduleUpdateData['address_type'] = $data['address_type']; } if (isset($data['service_type_id'])) { $customScheduleUpdateData['service_type_id'] = $data['service_type_id']; } if (isset($data['description'])) { $customScheduleUpdateData['description'] = $data['description']; } if (isset($data['min_price'])) { $customScheduleUpdateData['min_price'] = $data['min_price']; } if (isset($data['max_price'])) { $customScheduleUpdateData['max_price'] = $data['max_price']; } if (isset($data['offers_meal'])) { $customScheduleUpdateData['offers_meal'] = $data['offers_meal']; } if (!empty($customScheduleUpdateData)) { $customSchedule->update($customScheduleUpdateData); } if (isset($data['speciality_ids'])) { $custom_schedule = CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id); $custom_schedule->delete(); foreach ($data['speciality_ids'] as $specialityId) { CustomScheduleSpeciality::create([ 'custom_schedule_id' => $customSchedule->id, 'speciality_id' => $specialityId, ]); } } DB::commit(); return $customSchedule->fresh([ 'schedule.client.user', 'schedule.address', 'serviceType', 'specialities.speciality' ]); } catch (\Exception $e) { DB::rollBack(); Log::error('Error updating custom schedule: ' . $e->getMessage()); throw $e; } } public function delete($id) { DB::beginTransaction(); try { $customSchedule = CustomSchedule::findOrFail($id); $schedule = $customSchedule->schedule; CustomScheduleSpeciality::where('custom_schedule_id', $customSchedule->id)->delete(); $customSchedule->delete(); $schedule->delete(); DB::commit(); return $customSchedule; } catch (\Exception $e) { DB::rollBack(); Log::error('Error deleting custom schedule: ' . $e->getMessage()); throw $e; } } public function getSchedulesCustomGroupedByClient() { $schedules = Schedule::with(['client.user', 'provider.user', 'address', 'customSchedule.serviceType', 'customSchedule.specialities']) ->orderBy('id', 'desc') ->where('schedule_type', 'custom') ->get(); $grouped = $this->formatCustomSchedules($schedules); return $grouped; } public function getAvailableOpportunities($providerId) { $opportunities = Schedule::with([ 'client.user', 'address', 'customSchedule.serviceType', 'customSchedule.specialities' ]) ->leftJoin('schedule_refuses', function ($join) use ($providerId) { $join->on('schedules.id', '=', 'schedule_refuses.schedule_id') ->where('schedule_refuses.provider_id', $providerId); }) ->whereNull('schedule_refuses.id') ->where('schedules.schedule_type', 'custom') ->where('schedules.status', 'pending') ->whereNull('schedules.provider_id') ->select('schedules.*') ->get(); $availableOpportunities = $opportunities->filter(function ($opportunity) use ($providerId) { try { return $this->checkProviderAvailability($providerId, $opportunity); } catch (\Exception $e) { return false; } }); return $availableOpportunities->values(); } public function getProviderProposals($providerId) { return ScheduleProposal::with([ 'schedule.client.user', 'schedule.address', 'schedule.address.city', 'schedule.address.state', 'schedule.customSchedule.serviceType', 'schedule.customSchedule.specialities', 'schedule.provider.user' ]) ->where('provider_id', $providerId) ->orderBy('created_at', 'desc') ->get(); } public function getOpportunityProposals($scheduleId) { return ScheduleProposal::with(['provider.user']) ->where('schedule_id', $scheduleId) ->orderBy('created_at', 'desc') ->get(); } public function proposeOpportunity($scheduleId, $providerId) { $schedule = Schedule::findOrFail($scheduleId); if ($schedule->provider_id) { throw new \Exception(__('validation.custom.opportunity.already_assigned')); } $existingProposal = ScheduleProposal::where('schedule_id', $scheduleId) ->where('provider_id', $providerId) ->first(); if ($existingProposal) { throw new \Exception(__('validation.custom.opportunity.proposal_already_sent')); } $wasRefused = ScheduleRefuse::where('schedule_id', $scheduleId) ->where('provider_id', $providerId) ->exists(); if ($wasRefused) { throw new \Exception(__('validation.custom.opportunity.provider_refused')); } $this->checkProviderAvailability($providerId, $schedule); return ScheduleProposal::create([ 'schedule_id' => $scheduleId, 'provider_id' => $providerId, ]); } public function acceptProposal($proposalId) { return DB::transaction(function () use ($proposalId) { $proposal = ScheduleProposal::findOrFail($proposalId); $schedule = $proposal->schedule; if ($schedule->provider_id) { throw new \Exception(__('validation.custom.opportunity.already_assigned')); } $provider = Provider::find($proposal->provider_id); switch ($schedule->period_type) { case '8': $baseAmount = $provider->daily_price_8h; break; case '6': $baseAmount = $provider->daily_price_6h; break; case '4': $baseAmount = $provider->daily_price_4h; break; case '2': $baseAmount = $provider->daily_price_2h; break; default: } $schedule->total_amount = $baseAmount; $schedule->save(); $schedule->update([ 'provider_id' => $proposal->provider_id, 'status' => 'accepted', ]); ScheduleProposal::where('schedule_id', $schedule->id) ->where('id', '!=', $proposalId) ->delete(); return $schedule->fresh(['provider.user']); }); } public function refuseProposal($proposalId) { return DB::transaction(function () use ($proposalId) { $proposal = ScheduleProposal::findOrFail($proposalId); ScheduleRefuse::create([ 'schedule_id' => $proposal->schedule_id, 'provider_id' => $proposal->provider_id, ]); $proposal->delete(); return true; }); } private function checkProviderAvailability($providerId, $schedule) { $date = Carbon::parse($schedule->date); $startTime = $schedule->start_time; $endTime = $schedule->end_time; $dayOfWeek = $date->dayOfWeek; ScheduleBusinessRules::validateWeeklyScheduleLimit( $schedule->client_id, $providerId, $schedule->date ); $startHour = (int) substr($startTime, 0, 2); $endHour = (int) substr($endTime, 0, 2); $periods = []; if ($startHour < 13) { $periods[] = 'morning'; } if ($endHour >= 13 || ($startHour < 13 && $endHour > 12)) { $periods[] = 'afternoon'; } foreach ($periods as $period) { $workingDay = ProviderWorkingDay::where('provider_id', $providerId) ->where('day', $dayOfWeek) ->where('period', $period) ->first(); if (!$workingDay) { throw new \Exception(__('validation.custom.opportunity.provider_not_working')); } } $blockedDay = ProviderBlockedDay::where('provider_id', $providerId) ->where('date', $date->format('Y-m-d')) ->where(function ($query) use ($startTime, $endTime) { $query->where('period', 'full') ->orWhere(function ($q) use ($startTime, $endTime) { $q->where('period', 'partial') ->where(function ($q2) use ($startTime, $endTime) { $q2->whereBetween('init_hour', [$startTime, $endTime]) ->orWhereBetween('end_hour', [$startTime, $endTime]) ->orWhere(function ($q3) use ($startTime, $endTime) { $q3->where('init_hour', '<=', $startTime) ->where('end_hour', '>=', $endTime); }); }); }); }) ->first(); if ($blockedDay) { throw new \Exception(__('validation.custom.opportunity.provider_blocked')); } $excluded_status = ['cancelled', 'rejected']; $conflictingSchedule = Schedule::where('provider_id', $providerId) ->where('date', $date->format('Y-m-d')) ->whereNotIn('status', $excluded_status) ->where(function ($query) use ($startTime, $endTime) { $query->whereBetween('start_time', [$startTime, $endTime]) ->orWhereBetween('end_time', [$startTime, $endTime]) ->orWhere(function ($q) use ($startTime, $endTime) { $q->where('start_time', '<=', $startTime) ->where('end_time', '>=', $endTime); }); }) ->first(); if ($conflictingSchedule) { throw new \Exception(__('validation.custom.opportunity.schedule_conflict')); } return true; } public function getProvidersProposalsAndOpportunities($providerId) { $proposals = $this->getProviderProposals($providerId); $opportunities = $this->formatCustomSchedules($this->getAvailableOpportunities($providerId)); return [ 'proposals' => $proposals, 'opportunities' => $opportunities, ]; } public function formatCustomSchedules($schedules) { $grouped = $schedules->groupBy('client_id')->map(function ($clientSchedules) { $firstSchedule = $clientSchedules->first(); return [ 'client_id' => $firstSchedule->client_id, 'client_name' => $firstSchedule->client->user->name ?? 'N/A', 'schedules' => $clientSchedules->map(function ($schedule) { $customSchedule = $schedule->customSchedule; return [ 'id' => $schedule->id, 'date' => $schedule->date ? Carbon::parse($schedule->date)->format('d/m/Y') : null, 'start_time' => $schedule->start_time, 'end_time' => $schedule->end_time, 'period_type' => $schedule->period_type, 'status' => $schedule->status, 'total_amount' => $schedule->total_amount, 'code' => $schedule->code, 'code_verified' => $schedule->code_verified, 'provider_id' => $schedule->provider_id, 'provider_name' => $schedule->provider?->user->name ?? 'N/A', 'address' => $schedule->address ? [ 'id' => $schedule->address->id, 'address' => $schedule->address->address, 'complement' => $schedule->address->complement, 'zip_code' => $schedule->address->zip_code, 'city' => $schedule->address->city->name ?? '', 'state' => $schedule->address->city->state->name ?? '', ] : null, 'client_name' => $schedule->client->user->name ?? 'N/A', 'custom_schedule' => $customSchedule ? [ 'id' => $customSchedule->id, 'address_type' => $customSchedule->address_type, 'service_type_id' => $customSchedule->service_type_id, 'service_type_name' => $customSchedule->serviceType?->description ?? 'N/A', 'description' => $customSchedule->description, 'min_price' => $customSchedule->min_price, 'max_price' => $customSchedule->max_price, 'offers_meal' => $customSchedule->offers_meal, 'specialities' => $customSchedule->specialities->map(function ($speciality) { return [ 'id' => $speciality->id, 'description' => $speciality->description, ]; })->values() ] : null, ]; })->values() ]; })->sortBy('id')->values(); return $grouped; } public function verifyScheduleCode($scheduleId, $code) { $schedule = Schedule::findOrFail($scheduleId); if ($schedule->code_verified) { throw new \Exception(__('validation.custom.opportunity.code_already_verified')); } if ($schedule->code !== $code) { throw new \Exception(__('validation.custom.opportunity.invalid_code')); } $schedule->update([ 'code_verified' => true, 'status' => 'started', ]); return $schedule; } public function refuseOpportunity($scheduleId, $providerId) { $schedule_refuse = ScheduleRefuse::create([ 'schedule_id' => $scheduleId, 'provider_id' => $providerId, ]); return $schedule_refuse; } }