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', 'reviews.reviewsImprovements.improvementType']) ->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) { $client_id = $schedule->client_id; $provider_id = $providerId; $date = Carbon::parse($schedule->date); $dayOfWeek = $date->dayOfWeek;//0-6 $startTime = $schedule->start_time; $endTime = $schedule->end_time; $date_ymd = $date->format('Y-m-d'); $period = $startTime < '13:00:00' ? 'morning' : 'afternoon'; $period_type = $schedule->period_type;//2,4,6,8 // bloqueio 2 schedules por semana para o mesmo client e provider ScheduleBusinessRules::validateWeeklyScheduleLimit( $client_id, $provider_id, $date_ymd ); // bloqueio provider trabalha no dia/periodo ScheduleBusinessRules::validateWorkingDay( $provider_id, $dayOfWeek, $period ); // bloqueio provider tem blockedday para dia/hora ScheduleBusinessRules::validateBlockedDay( $provider_id, $date_ymd, $startTime, $endTime ); // bloqueio daily_price do provider esta fora do range min_price e max_price ScheduleBusinessRules::validatePricePeriod( $provider_id, $schedule->customSchedule->min_price, $schedule->customSchedule->max_price, $period_type ); // bloqueio provider tem outro agendamento para dia/hora ScheduleBusinessRules::validateConflictingSchedule( $provider_id, $date_ymd, $startTime, $endTime ); // bloqueio provider tem outra proposta para o mesmo agendamento ScheduleBusinessRules::validateConflictingSameProposal( $provider_id, $schedule->id ); // bloqueio provider tem outra proposta na mesma data ScheduleBusinessRules::validateConflictingProposalSameDate( $provider_id, $date_ymd, $startTime, $endTime, $schedule->id ); // bloqueio caso o client tenha bloqueado o provider ScheduleBusinessRules::validateClientNotBlockedByProvider( $client_id, $provider_id ); // bloqueio caso o provider tenha bloqueado o client ScheduleBusinessRules::validateProviderNotBlockedByClient( $client_id, $provider_id ); 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, 'client_id' => $schedule->client_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, 'reviews' => $schedule->reviews->map(function ($review) { return [ 'id' => $review->id, 'stars' => $review->stars, 'comment' => $review->comment, 'origin' => $review->origin, 'origin_id' => $review->origin_id, 'created_at' => Carbon::parse($review->created_at)->format('Y-m-d H:i'), 'updated_at' => Carbon::parse($review->updated_at)->format('Y-m-d H:i'), 'improvements' => $review->reviewsImprovements->map(function ($ri) { return [ 'id' => $ri->id, 'improvement_type_id' => $ri->improvement_type_id, 'improvement_type_name' => $ri->improvementType ? $ri->improvementType->description : null, ]; })->values(), ]; }) ]; })->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, ]); return $schedule; } public function refuseOpportunity($scheduleId, $providerId) { $schedule_refuse = ScheduleRefuse::create([ 'schedule_id' => $scheduleId, 'provider_id' => $providerId, ]); return $schedule_refuse; } }