feat(schedule): refactor timeslot management and fix quality blockers#88
Merged
CFBruna merged 130 commits intov2/monorepo-migrationfrom Mar 2, 2026
Merged
feat(schedule): refactor timeslot management and fix quality blockers#88CFBruna merged 130 commits intov2/monorepo-migrationfrom
CFBruna merged 130 commits intov2/monorepo-migrationfrom
Conversation
- One file per model: service.py, appointment.py, timeslot.py - SizeCategory TextChoices added to service.py - Service: added is_active, requires_veterinarian, has_size_pricing, price/duration per size category, created_at, updated_at - Appointment: removed unique=True from schedule_time (critical bug fix), added NO_SHOW status, price_charged, pet_weight_at_booking, size_category_at_booking, canceled_at, created_at, updated_at, DB indexes - TimeSlot: added is_active field - __str__ uses django.utils.formats (no hardcoded strftime locale)
…yped exceptions - exceptions.py: typed exception hierarchy (ScheduleException base) - appointment_service.py: create/update/cancel/complete + _derive_size_category auto-snapshots pet weight, size_category and price_charged at booking - slot_service.py: get_available_slots respects is_active on TimeSlot, uses pet weight for size-correct duration, zero circular imports - service_service.py: get_all_active, get_by_id, create_service, update_service - __init__.py: clean public API with __all__
- service_admin.py: @admin.register(Service) with fieldsets for base/size pricing and audit fields; price_display via currency_tags (no hardcoded symbols) - appointment_admin.py: @admin.register(Appointment) with new snapshot fields in list_display (price_charged, size_category_at_booking, pet_weight_at_booking) and readonly_fields - timeslot_admin.py: @admin.register(TimeSlot) with is_active column - __init__.py: clean imports with __all__
views.py: - AvailableSlotsView uses ServiceService.get_by_id (zero ORM) - AppointmentViewSet.perform_destroy delegates to AppointmentService.cancel_appointment (preserves history, no hard delete) - All except-ValueError replaced by typed ScheduleException subtypes - Added /cancel/ action endpoint - Service queryset filtered by is_active=True serializers.py: - AppointmentSerializer exposes all snapshot fields as read_only - SlotService.get_available_slots receives pet for size-correct duration - ServiceSerializer includes all new fields (is_active, has_size_pricing, size fields) factories.py: - ServiceFactory: is_active=True, has_size_pricing=False - AppointmentFactory: price_charged snapshot field - TimeSlotFactory: is_active=True - SERVICE_NAMES extracted as module-level constant (no inline lambda nesting)
…chitecture test_models.py: - TestServiceModel: defaults, get_price_for_size, get_duration_for_size, fallback-to-base when size price is null, created_at/updated_at - TestTimeSlotModel: is_active default, __str__ uses localised format - TestAppointmentModel: snapshot fields nullable, canceled_at nullable, created_at auto-set, __str__ includes pet and service names test_service.py: - TestDeriveSizeCategory: boundary tests 0-5/5-20/20-45/45+ kg, None weight - TestCreateAppointment: snapshots weight+size+price, PastSchedulingError, SamedayAppointmentError, two pets same time allowed - TestCancelAppointment: sets canceled_at, AlreadyCanceledError, CancellationWindowError for non-staff < 1h, staff bypasses window - TestCompleteAppointment: sets completed_at, FutureCompletionError - TestSlotService: past date returns [], inactive slot excluded, active slot included, occupied slot excluded
…intmentAdmin autocomplete_fields requires Pet to have a registered admin with search_fields. raw_id_fields achieves the same FK lookup UX without that hard dependency.
1. forms.py: replace AppointmentService.get_available_slots -> SlotService.get_available_slots (2 call sites: get_dynamic_time_choices + clean); add ScheduleException to except clause; use .astimezone(active_tz).time() for correct local-time comparison 2. appointment_service.py: schedule_time.date() -> timezone.localtime(schedule_time).date() on all 3 sameday filter sites (create/update/prepare). Root cause: .date() on a UTC-aware datetime returns UTC date; Django __date lookup compares using settings.TIME_ZONE. At 21h UTC in Paraguay (-3h), now+2h=23:36 UTC and now+3h=00:36 UTC are different UTC dates even though they are the same local day. 3. test_api.py: update direct call from AppointmentService.get_available_slots -> SlotService.get_available_slots + fix import 4. test_models.py: test_unique_together_day_start_time must use TimeSlot.objects.create() directly; TimeSlotFactory has django_get_or_create which silently returns the existing object instead of raising IntegrityError. test_service.py: remove unnecessary pytz import (stdlib zoneinfo suffices)
…_BR/es locale files exceptions.py: - FutureCompletionError: 'completed' -> "'Completed'" to match pre-existing test assertion locale/pt_BR/LC_MESSAGES/django.po: - All new strings: models (Service, Appointment, TimeSlot, SizeCategory), admin fieldset titles (Base Pricing, Size-Based Pricing, Audit), service exception messages, form validation messages locale/es/LC_MESSAGES/django.po: - Same strings in Spanish After pulling, run locally: uv run python manage.py compilemessages
…onents, page, i18n
…pointments routes
…ks from TimeSlotsManagementPage
- Add `list_available` to EmployeeService with conflict filtering - Expand EmployeeViewSet with create/update/destroy methods - Create `EmployeeAvailableView` to expose efficient availability querying - Build EmployeesPage, FormModal and Table components in React - Bind `useEmployeeAvailability` to AppointmentForm dropdown conditional fetching - Inject i18n locales for Employee translation keys (EN, BR, ES)
- Fix remaining E402 and E701 linter warnings - Ensure correct import topology inside tests to satisfy pre-commit hooks
- Add 'Equipe' to the scheduling sidebar group - Unify translation keys under 'employees_management' object - Fix duplicate 'employees' keys in JSON locales for valid structure - Internationalize Table status badges and add missing noEmployeeAvailable string
…rinarian role check
- AccountsError.__init__ now accepts field kwarg - Replace dict with OpenApiParameter in by_role action - Fix unit_price None in appointment_service.link_sale_to_appointment - Replace deprecated make_random_password with secrets.token_urlsafe - Fix timedelta*F mypy operator error with type: ignore - Fix AppointmentFactory.pk mypy attr-defined with typed variables - Add LOCALE_PATHS and filterset_fields type annotations - Fix service.py dict.get call-overload mypy errors - SaleAdmin.change_view now raises PermissionDenied (fixes TestSaleAdmin 403) - Add testpaths=[src] to pytest config to exclude debug scripts
… correct HTML output
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
TimeSlotModalcomponent and dedicatedTimeSlotsManagementPagemoved topages/timezone.localtime()usage in slot validationget_veterinarian_category_idwithEmployeeService.is_veterinarian(role)in accounts serializers and viewsLocaleEnforcementMiddlewareposition inMIDDLEWARE— must come afterSessionMiddlewareAccountsError.fieldkwarg,OpenApiParametertype,unit_pricefallback,timedelta * F,AppointmentFactory.pk, deprecatedmake_random_passwordSaleAdmin.change_viewnow raisesPermissionDenied— fixes two failing admin tests expecting 403set-state-in-effectin employee form modals with expliciteslint-disable-linetestpaths = ["src"]to pytest config to exclude debug scripts from collectionWhy
The timeslot management page needed a dedicated modal and page separation to align with the
project's component architecture. Several mypy, ruff, and pytest violations blocked the CI/CD
pipeline from passing and had to be resolved before opening a PR.
How to Test
pt-brbrowser cookie first)/admin/timeslots/manageand verify theTimeSlotModalopens and saves correctlyuv run pytest— expect 397/397 passednpm run build— expect success with no errorsuv run mypy src/— expect 0 errorsChecklist
uv run pytestpassesnpm run buildpasses