16
16
from django .core .validators import MaxLengthValidator , MinLengthValidator , MinValueValidator
17
17
from django .db import models
18
18
from django .urls import reverse
19
+ from django .utils import timezone
19
20
from django .utils .translation import gettext_lazy as _
20
21
from phonenumber_field .modelfields import PhoneNumberField
21
22
@@ -70,6 +71,14 @@ class Service(models.Model):
70
71
image = models .ImageField (upload_to = 'services/' , blank = True , null = True )
71
72
currency = models .CharField (max_length = 3 , default = 'USD' , validators = [MaxLengthValidator (3 ), MinLengthValidator (3 )])
72
73
background_color = models .CharField (max_length = 50 , null = True , blank = True , default = "" )
74
+ reschedule_limit = models .PositiveIntegerField (
75
+ default = 0 ,
76
+ help_text = _ ("Maximum number of times an appointment can be rescheduled." )
77
+ )
78
+ allow_rescheduling = models .BooleanField (
79
+ default = False ,
80
+ help_text = _ ("Indicates whether appointments for this service can be rescheduled." )
81
+ )
73
82
74
83
# meta data
75
84
created_at = models .DateTimeField (auto_now_add = True )
@@ -88,6 +97,8 @@ def save(self, *args, **kwargs):
88
97
# price shouldn't be negative
89
98
if self .price < 0 :
90
99
raise ValidationError (_ ("Price cannot be negative" ))
100
+ if self .down_payment < 0 :
101
+ raise ValidationError (_ ("Down payment cannot be negative" ))
91
102
if self .background_color == "" :
92
103
self .background_color = generate_rgb_color ()
93
104
return super ().save (* args , ** kwargs )
@@ -287,6 +298,7 @@ class AppointmentRequest(models.Model):
287
298
staff_member = models .ForeignKey (StaffMember , on_delete = models .SET_NULL , null = True )
288
299
payment_type = models .CharField (max_length = 4 , choices = PAYMENT_TYPES , default = 'full' )
289
300
id_request = models .CharField (max_length = 100 , blank = True , null = True )
301
+ reschedule_attempts = models .PositiveIntegerField (default = 0 )
290
302
291
303
# meta data
292
304
created_at = models .DateTimeField (auto_now_add = True )
@@ -301,7 +313,6 @@ def clean(self):
301
313
raise ValueError (_ ("Start time must be before end time" ))
302
314
if self .start_time == self .end_time :
303
315
raise ValueError (_ ("Start time and end time cannot be the same" ))
304
-
305
316
# Check for valid date
306
317
try :
307
318
# This will raise a ValueError if the date is not valid
@@ -359,6 +370,76 @@ def is_a_paid_service(self):
359
370
def accepts_down_payment (self ):
360
371
return self .service .accepts_down_payment ()
361
372
373
+ def can_be_rescheduled (self ):
374
+ return self .reschedule_attempts < self .service .reschedule_limit
375
+
376
+ def increment_reschedule_attempts (self ):
377
+ self .reschedule_attempts += 1
378
+ self .save (update_fields = ['reschedule_attempts' ])
379
+
380
+ def get_reschedule_history (self ):
381
+ return self .reschedule_histories .all ().order_by ('-created_at' )
382
+
383
+
384
+ class AppointmentRescheduleHistory (models .Model ):
385
+ appointment_request = models .ForeignKey (
386
+ 'AppointmentRequest' ,
387
+ on_delete = models .CASCADE , related_name = 'reschedule_histories'
388
+ )
389
+ date = models .DateField (help_text = _ ("The previous date of the appointment before it was rescheduled." ))
390
+ start_time = models .TimeField (
391
+ help_text = _ ("The previous start time of the appointment before it was rescheduled." )
392
+ )
393
+ end_time = models .TimeField (
394
+ help_text = _ ("The previous end time of the appointment before it was rescheduled." )
395
+ )
396
+ staff_member = models .ForeignKey (
397
+ StaffMember , on_delete = models .SET_NULL , null = True ,
398
+ help_text = _ ("The previous staff member of the appointment before it was rescheduled." )
399
+ )
400
+ reason_for_rescheduling = models .TextField (
401
+ blank = True , null = True ,
402
+ help_text = _ ("Reason for the appointment reschedule." )
403
+ )
404
+ reschedule_status = models .CharField (
405
+ max_length = 10 ,
406
+ choices = [('pending' , 'Pending' ), ('confirmed' , 'Confirmed' )],
407
+ default = 'pending' ,
408
+ help_text = _ ("Indicates the status of the reschedule action." )
409
+ )
410
+ id_request = models .CharField (max_length = 100 , blank = True , null = True )
411
+
412
+ # meta data
413
+ created_at = models .DateTimeField (auto_now_add = True , help_text = _ ("The date and time the reschedule was recorded." ))
414
+ updated_at = models .DateTimeField (auto_now = True , help_text = _ ("The date and time the reschedule was confirmed." ))
415
+
416
+ class Meta :
417
+ verbose_name = _ ("Appointment Reschedule History" )
418
+ verbose_name_plural = _ ("Appointment Reschedule Histories" )
419
+ ordering = ['-created_at' ]
420
+
421
+ def __str__ (self ):
422
+ return f"Reschedule history for { self .appointment_request } from { self .date } "
423
+
424
+ def save (self , * args , ** kwargs ):
425
+ # if no id_request is provided, generate one
426
+ if self .id_request is None :
427
+ self .id_request = f"{ get_timestamp ()} { generate_random_id ()} "
428
+ # date should not be in the past
429
+ if self .date < datetime .date .today ():
430
+ raise ValidationError (_ ("Date cannot be in the past" ))
431
+ try :
432
+ datetime .datetime .strptime (str (self .date ), '%Y-%m-%d' )
433
+ except ValueError :
434
+ raise ValidationError (_ ("The date is not valid" ))
435
+ return super ().save (* args , ** kwargs )
436
+
437
+ def still_valid (self ):
438
+ # if more than 5 minutes have passed, it is no longer valid
439
+ now = timezone .now () # This is offset-aware to match self.created_at
440
+ delta = now - self .created_at
441
+ return delta .total_seconds () < 300
442
+
362
443
363
444
class Appointment (models .Model ):
364
445
"""
@@ -578,8 +659,19 @@ class Config(models.Model):
578
659
default = "" ,
579
660
help_text = _ ("Name of your website." ),
580
661
)
581
- app_offered_by_label = models .CharField (max_length = 255 , default = _ ("Offered by" ),
582
- help_text = _ ("Label for `Offered by` on the appointment page" ))
662
+ app_offered_by_label = models .CharField (
663
+ max_length = 255 ,
664
+ default = _ ("Offered by" ),
665
+ help_text = _ ("Label for `Offered by` on the appointment page" )
666
+ )
667
+ default_reschedule_limit = models .PositiveIntegerField (
668
+ default = 3 ,
669
+ help_text = _ ("Default maximum number of times an appointment can be rescheduled across all services." )
670
+ )
671
+ allow_staff_change_on_reschedule = models .BooleanField (
672
+ default = True ,
673
+ help_text = _ ("Allows clients to change the staff member when rescheduling an appointment." )
674
+ )
583
675
584
676
# meta data
585
677
created_at = models .DateTimeField (auto_now_add = True )
0 commit comments