3
3
import string
4
4
5
5
from django .core .exceptions import ValidationError
6
- from django .core .validators import RegexValidator
6
+ from django .core .validators import RegexValidator , MinValueValidator , MinLengthValidator , MaxLengthValidator
7
7
from django .db import models
8
8
from django .utils .translation import gettext_lazy as _
9
9
22
22
23
23
24
24
class Service (models .Model ):
25
+ """
26
+ Represents a service provided by the appointment system.
27
+
28
+ Author: Adams Pierre David
29
+ Version: 1.1.0
30
+ Since: 1.0.0
31
+ """
25
32
name = models .CharField (max_length = 100 )
26
33
description = models .TextField (blank = True , null = True )
27
34
duration = models .DurationField ()
28
- price = models .DecimalField (max_digits = 6 , decimal_places = 2 )
29
- down_payment = models .DecimalField (max_digits = 6 , decimal_places = 2 , default = 0 )
30
- currency = models .CharField (max_length = 3 , default = 'USD' )
35
+ price = models .DecimalField (max_digits = 6 , decimal_places = 2 , validators = [ MinValueValidator ( 0 )] )
36
+ down_payment = models .DecimalField (max_digits = 6 , decimal_places = 2 , default = 0 , validators = [ MinValueValidator ( 0 )] )
37
+ currency = models .CharField (max_length = 3 , default = 'USD' , validators = [ MaxLengthValidator ( 3 ), MinLengthValidator ( 3 )] )
31
38
image = models .ImageField (upload_to = 'services/' , blank = True , null = True )
32
39
33
40
# meta data
@@ -43,21 +50,33 @@ def get_name(self):
43
50
def get_description (self ):
44
51
return self .description
45
52
53
+ def get_duration_parts (self ):
54
+ total_seconds = int (self .duration .total_seconds ())
55
+ days = total_seconds // 86400
56
+ hours = (total_seconds % 86400 ) // 3600
57
+ minutes = (total_seconds % 3600 ) // 60
58
+ seconds = total_seconds % 60
59
+ return days , hours , minutes , seconds
60
+
46
61
def get_duration (self ):
47
- total_seconds = self .duration .total_seconds ()
48
- if total_seconds >= 86400 : # 1 day = 86400 seconds
49
- days = total_seconds // 86400
50
- return f"{ days } day{ 's' if days > 1 else '' } "
51
- elif total_seconds >= 3600 : # 1 hour = 3600 seconds
52
- hours = total_seconds // 3600
53
- return f"{ hours } hour{ 's' if hours > 1 else '' } "
54
- elif total_seconds >= 60 : # 1 minute = 60 seconds
55
- minutes = total_seconds // 60
56
- return f"{ minutes } minute{ 's' if minutes > 1 else '' } "
57
- else :
58
- return f"{ total_seconds } second{ 's' if total_seconds > 1 else '' } "
62
+ days , hours , minutes , seconds = self .get_duration_parts ()
63
+ parts = []
64
+
65
+ if days :
66
+ parts .append (f"{ days } day{ 's' if days > 1 else '' } " )
67
+ if hours :
68
+ parts .append (f"{ hours } hour{ 's' if hours > 1 else '' } " )
69
+ if minutes :
70
+ parts .append (f"{ minutes } minute{ 's' if minutes > 1 else '' } " )
71
+ if seconds :
72
+ parts .append (f"{ seconds } second{ 's' if seconds > 1 else '' } " )
73
+
74
+ return ' ' .join (parts )
59
75
60
76
def get_price (self ):
77
+ return self .price
78
+
79
+ def get_price_text (self ):
61
80
if self .price == 0 :
62
81
return "Free"
63
82
else :
@@ -89,6 +108,13 @@ def accepts_down_payment(self):
89
108
90
109
91
110
class AppointmentRequest (models .Model ):
111
+ """
112
+ Represents an appointment request made by a client.
113
+
114
+ Author: Adams Pierre David
115
+ Version: 1.1.0
116
+ Since: 1.0.0
117
+ """
92
118
date = models .DateField ()
93
119
start_time = models .TimeField ()
94
120
end_time = models .TimeField ()
@@ -103,6 +129,11 @@ class AppointmentRequest(models.Model):
103
129
def __str__ (self ):
104
130
return f"{ self .date } - { self .start_time } to { self .end_time } - { self .service .name } "
105
131
132
+ def clean (self ):
133
+ if self .start_time is not None and self .end_time is not None :
134
+ if self .start_time >= self .end_time :
135
+ raise ValueError ("Start time must be before end time" )
136
+
106
137
def save (self , * args , ** kwargs ):
107
138
if self .id_request is None :
108
139
self .id_request = f"{ Utility .get_timestamp ()} { self .service .id } { Utility .generate_random_id ()} "
@@ -161,6 +192,13 @@ def get_updated_at(self):
161
192
162
193
163
194
class Appointment (models .Model ):
195
+ """
196
+ Represents an appointment made by a client. It is created when the client confirms the appointment request.
197
+
198
+ Author: Adams Pierre David
199
+ Version: 1.1.0
200
+ Since: 1.0.0
201
+ """
164
202
client = models .ForeignKey (APPOINTMENT_CLIENT_MODEL , on_delete = models .CASCADE )
165
203
appointment_request = models .OneToOneField (AppointmentRequest , on_delete = models .CASCADE )
166
204
phone = models .CharField (validators = [phone_regex ], max_length = 10 , blank = True , null = True , default = "" ,
@@ -262,6 +300,14 @@ def set_appointment_paid_status(self, status: bool):
262
300
263
301
264
302
class Config (models .Model ):
303
+ """
304
+ Represents configuration settings for the appointment system. There can only be one Config object in the database.
305
+ If you want to change the settings, you must edit the existing Config object.
306
+
307
+ Author: Adams Pierre David
308
+ Version: 1.1.0
309
+ Since: 1.1.0
310
+ """
265
311
slot_duration = models .PositiveIntegerField (
266
312
null = True ,
267
313
help_text = _ ("Minimum time for an appointment in minutes, recommended 30." ),
@@ -274,7 +320,7 @@ class Config(models.Model):
274
320
null = True ,
275
321
help_text = _ ("Time when we stop working." ),
276
322
)
277
- appointment_buffer_time = models .DurationField (
323
+ appointment_buffer_time = models .FloatField (
278
324
null = True ,
279
325
help_text = _ ("Time between now and the first available slot for the current day (doesn't affect tomorrow)." ),
280
326
)
@@ -287,6 +333,9 @@ class Config(models.Model):
287
333
def clean (self ):
288
334
if Config .objects .exists () and not self .pk :
289
335
raise ValidationError (_ ("You can only create one Config object" ))
336
+ if self .lead_time is not None and self .finish_time is not None :
337
+ if self .lead_time >= self .finish_time :
338
+ raise ValidationError (_ ("Lead time must be before finish time" ))
290
339
291
340
def save (self , * args , ** kwargs ):
292
341
self .clean ()
@@ -298,6 +347,13 @@ def __str__(self):
298
347
299
348
300
349
class PaymentInfo (models .Model ):
350
+ """
351
+ Represents payment information for an appointment.
352
+
353
+ Author: Adams Pierre David
354
+ Version: 1.1.0
355
+ Since: 1.0.0
356
+ """
301
357
appointment = models .ForeignKey (Appointment , on_delete = models .CASCADE )
302
358
303
359
# meta data
@@ -333,6 +389,13 @@ def get_user_email(self):
333
389
334
390
335
391
class EmailVerificationCode (models .Model ):
392
+ """
393
+ Represents an email verification code for a user when the email already exists in the database.
394
+
395
+ Author: Adams Pierre David
396
+ Version: 1.1.0
397
+ Since: 1.1.0
398
+ """
336
399
user = models .ForeignKey (APPOINTMENT_CLIENT_MODEL , on_delete = models .CASCADE )
337
400
code = models .CharField (max_length = 6 )
338
401
@@ -348,4 +411,4 @@ def generate_code(cls, user):
348
411
code = '' .join (random .choices (string .ascii_uppercase + string .digits , k = 6 ))
349
412
verification_code = cls (user = user , code = code )
350
413
verification_code .save ()
351
- return code
414
+ return code
0 commit comments