Skip to content

Commit 0d4e1d8

Browse files
committed
[IMP] appointment_capacity: added test cases for usr/resource remaining capacity
1 parent 5aab045 commit 0d4e1d8

File tree

10 files changed

+602
-50
lines changed

10 files changed

+602
-50
lines changed

appointment_capacity/__manifest__.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
{
2-
'name': 'Appointment Capacity Management',
3-
'version': '1.0',
4-
'summary': 'Enhance appointment booking with multiple bookings and seats per slot.',
5-
'description': """
2+
"name": "Appointment Capacity Management",
3+
"version": "1.0",
4+
"summary": "Enhance appointment booking with multiple bookings and seats per slot.",
5+
"description": """
66
- Allows multiple bookings per time slot.
77
- Supports multiple seats per slot.
88
""",
9-
'author': 'Darshan Patel',
10-
'depends': ['appointment','appointment_account_payment', 'calendar'],
11-
'data': [
12-
'views/appointment_type_views.xml',
13-
'views/appointment_template_appointment.xml',
14-
'views/calendar_event_views.xml',
9+
"author": "Darshan Patel",
10+
"depends": ["appointment", "appointment_account_payment", "calendar"],
11+
"data": [
12+
"views/appointment_type_views.xml",
13+
"views/appointment_template_appointment.xml",
14+
"views/calendar_event_views.xml",
1515
],
16-
'installable': True,
17-
'license': 'LGPL-3',
16+
"installable": True,
17+
"license": "LGPL-3",
1818
}

appointment_capacity/controllers/appointment.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616

1717
class AppointmentCapacityController(AppointmentController):
1818

19-
2019
def _prepare_appointment_type_page_values(self, appointment_type, staff_user_id=False, resource_selected_id=False, skip_resource_selection=False, **kwargs):
2120
"""
2221
Overrides `_prepare_appointment_type_page_values` to include capacity management based on `capacity_type`.
2322
"""
2423
values = super()._prepare_appointment_type_page_values(appointment_type, staff_user_id, resource_selected_id, **kwargs)
25-
24+
2625
if appointment_type.capacity_type == 'multiple_seats':
2726
if appointment_type.schedule_based_on == 'users':
2827
max_capacity_possible = appointment_type.user_capacity_count
@@ -31,10 +30,9 @@ def _prepare_appointment_type_page_values(self, appointment_type, staff_user_id=
3130
max_capacity_possible = possible_combinations[-1][1] if possible_combinations else 1
3231
else:
3332
max_capacity_possible = 1
34-
35-
values['max_capacity_possible'] = min(12, max_capacity_possible)
36-
return values
3733

34+
values['max_capacity'] = min(12, max_capacity_possible)
35+
return values
3836

3937
@http.route()
4038
def appointment_form_submit(self, appointment_type_id, datetime_str, duration_str, name, phone, email, staff_user_id=None, available_resource_ids=None, asked_capacity=1,
@@ -106,7 +104,6 @@ def appointment_form_submit(self, appointment_type_id, datetime_str, duration_st
106104
if users_remaining_capacity['total_remaining_capacity'] < asked_capacity and appointment_type.capacity_type != 'one_booking':
107105
return request.redirect('/appointment/%s?%s' % (appointment_type.id, keep_query('*', state='failed-staff-user')))
108106

109-
110107
guests = None
111108
if appointment_type.allow_guests:
112109
if guest_emails_str:
@@ -156,7 +153,7 @@ def appointment_form_submit(self, appointment_type_id, datetime_str, duration_st
156153
'partner_id': customer.id,
157154
}
158155

159-
for question in appointment_type.question_ids.filtered(lambda question: question.id in partner_inputs.keys()):
156+
for question in appointment_type.question_ids.filtered(lambda question: question.id in partner_inputs):
160157
if question.question_type == 'checkbox':
161158
answers = question.answer_ids.filtered(lambda answer: answer.id in partner_inputs[question.id])
162159
answer_input_values.extend([
@@ -181,7 +178,7 @@ def appointment_form_submit(self, appointment_type_id, datetime_str, duration_st
181178
booking_line_values.append({
182179
'appointment_resource_id': resource.id,
183180
'capacity_reserved': new_capacity_reserved,
184-
'capacity_used': new_capacity_reserved if resource.shareable and appointment_type.capacity_type != 'single_booking' else resource.capacity,
181+
'capacity_used': new_capacity_reserved if resource.shareable and appointment_type.capacity_type != 'one_booking' else resource.capacity,
185182
})
186183
else:
187184
user_remaining_capacity = users_remaining_capacity['total_remaining_capacity']

appointment_capacity/models/appointment_booking.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class AppointmentBookingLine(models.Model):
55
_inherit = "appointment.booking.line"
66

77
appointment_user_id = fields.Many2one('res.users', string="Appointment user", ondelete="cascade")
8-
appointment_resource_id = fields.Many2one('appointment.resource', string="Appointment Resource",ondelete="cascade",required=False)
8+
appointment_resource_id = fields.Many2one('appointment.resource', string="Appointment Resource", ondelete="cascade", required=False)
99

1010
@api.depends(
1111
"appointment_resource_id.capacity",
@@ -23,7 +23,7 @@ def _compute_capacity_used(self):
2323
line.capacity_used = 1
2424
elif line.appointment_type_id.capacity_type == 'multiple_seats':
2525
line.capacity_used = line.capacity_reserved
26-
elif (not line.appointment_resource_id.shareable or line.appointment_type_id.capacity_type == 'single_booking') and line.appointment_type_id.schedule_based_on == 'resources':
26+
elif (not line.appointment_resource_id.shareable or line.appointment_type_id.capacity_type == 'one_booking') and line.appointment_type_id.schedule_based_on == 'resources':
2727
line.capacity_used = line.appointment_resource_id.capacity
2828
else:
2929
line.capacity_used = line.capacity_reserved

appointment_capacity/models/appointment_type.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,20 @@ class AppointmentType(models.Model):
1818

1919
capacity_type = fields.Selection(
2020
[
21-
("single_booking", "Single Booking"),
21+
("one_booking", "One Booking"),
2222
("multiple_bookings", "Multiple Bookings"),
2323
("multiple_seats", "Multiple Seats"),
2424
],
2525
string="Capacity Type",
2626
required=True,
27-
default="single_booking",
27+
default="one_booking",
2828
)
29-
3029
user_capacity_count = fields.Integer(
3130
"User cpacity count",
3231
default=1,
3332
help="Maximum number of users bookings per slot (for multiple bookings or multiple seats).",
3433
)
3534

36-
3735
@api.constrains("user_capacity_count")
3836
def _check_user_capacity_count(self):
3937
for record in self:
@@ -91,7 +89,7 @@ def _slot_availability_is_resource_available(self, slot, resource, availability_
9189
slot_start_dt_utc, slot_end_dt_utc = slot['UTC'][0], slot['UTC'][1]
9290
resource_to_bookings = availability_values.get('resource_to_bookings')
9391
# Check if there is already a booking line for the time slot and make it available
94-
# only if the resource is shareable and the capacity_type is not single_booking.
92+
# only if the resource is shareable and the capacity_type is not one_booking.
9593
# This avoid to mark the resource as "available" and compute unnecessary remaining capacity computation
9694
# because of potential linked resources.
9795
if resource_to_bookings.get(resource):
@@ -108,7 +106,6 @@ def _slot_availability_is_resource_available(self, slot, resource, availability_
108106

109107
return True
110108

111-
112109
def _slot_availability_select_best_resources(self, capacity_info, asked_capacity):
113110
""" Check and select the best resources for the capacity needed
114111
:params main_resources_remaining_capacity <dict>: dict containing remaining capacities of resources available
@@ -120,7 +117,7 @@ def _slot_availability_select_best_resources(self, capacity_info, asked_capacity
120117
available_resources = self.env['appointment.resource'].concat(*capacity_info.keys()).sorted('sequence')
121118
if not available_resources:
122119
return self.env['appointment.resource']
123-
if self.capacity_type == 'single_booking':
120+
if self.capacity_type == 'one_booking':
124121
return available_resources[0] if self.assign_method != 'time_resource' else available_resources
125122

126123
perfect_matches = available_resources.filtered(
@@ -209,10 +206,9 @@ def _slots_fill_users_availability(self, slots, start_dt, end_dt, filter_users=N
209206
else:
210207
slot['staff_user_id'] = available_staff_users
211208

212-
213-
214209
def _slot_availability_is_user_available(self, slot, staff_user, availability_values, asked_capacity=1):
215-
""" This method verifies if the user is available on the given slot.
210+
"""
211+
This method verifies if the user is available on the given slot.
216212
It checks whether the user has calendar events clashing and if he
217213
is included in slot's restricted users.
218214
@@ -247,7 +243,7 @@ def _slot_availability_is_user_available(self, slot, staff_user, availability_va
247243
# return False
248244
for event in day_events:
249245
if not event.allday and (event.start < slot_end_dt_utc and event.stop > slot_start_dt_utc):
250-
if self.capacity_type != "single_booking" and self == event.appointment_type_id:
246+
if self.capacity_type != "one_booking" and self == event.appointment_type_id:
251247
if users_remaining_capacity['total_remaining_capacity'] >= asked_capacity:
252248
continue
253249
return False
@@ -261,7 +257,7 @@ def _slot_availability_is_user_available(self, slot, staff_user, availability_va
261257
return True
262258

263259
def _get_users_remaining_capacity(self, users, slot_start_utc, slot_stop_utc, availability_values=None):
264-
"""
260+
"""
265261
Compute the remaining capacity for users in a specific time slot.
266262
:param <res.users> users : record containing one or a multiple of user
267263
:param datetime slot_start_utc: start of slot (in naive UTC)
@@ -297,7 +293,7 @@ def _get_users_remaining_capacity(self, users, slot_start_utc, slot_stop_utc, av
297293
return users_remaining_capacity
298294

299295
def _slot_availability_prepare_users_values(self, staff_users, start_dt, end_dt):
300-
"""
296+
"""
301297
Override to add booking values.
302298
303299
:return: update ``super()`` values with users booking vaues, formatted like
@@ -312,11 +308,11 @@ def _slot_availability_prepare_users_values(self, staff_users, start_dt, end_dt)
312308

313309
def _slot_availability_prepare_users_bookings_values(self, users, start_dt_utc, end_dt_utc):
314310
"""
315-
This method retrieves and organizes bookings for the given users within the specified time range.
316-
Users may handle multiple appointment types, so all overlapping bookings must be considered
311+
This method retrieves and organizes bookings for the given users within the specified time range.
312+
Users may handle multiple appointment types, so all overlapping bookings must be considered
317313
to prevent double booking.
318314
319-
:param <res.users> users: A recordset of staff users for whom availability is being checked.
315+
:param <res.users> users: A recordset of staff users for whom availability is being checked.
320316
:param datetime start_dt_utc: The start of the appointment check boundary in UTC.
321317
:param datetime end_dt_utc: The end of the appointment check boundary in UTC.
322318
@@ -339,7 +335,8 @@ def _slot_availability_prepare_users_bookings_values(self, users, start_dt_utc,
339335
}
340336

341337
def _get_appointment_slots(self, timezone, filter_users=None, filter_resources=None, asked_capacity=1, reference_date=None):
342-
""" Fetch available slots to book an appointment.
338+
"""
339+
Fetch available slots to book an appointment.
343340
344341
:param str timezone: timezone string e.g.: 'Europe/Brussels' or 'Etc/GMT+1'
345342
:param <res.users> filter_users: filter available slots for those users (can be a singleton
@@ -390,13 +387,13 @@ def _get_appointment_slots(self, timezone, filter_users=None, filter_resources=N
390387
if self.category == 'custom' and unique_slots:
391388
# Custom appointment type, the first day should depend on the first slot datetime
392389
start_first_slot = unique_slots[0].start_datetime
393-
first_day_utc = start_first_slot if reference_date > start_first_slot else reference_date
390+
first_day_utc = min(reference_date, start_first_slot)
394391
first_day = requested_tz.fromutc(first_day_utc + relativedelta(hours=self.min_schedule_hours))
395392
appointment_duration_days = (unique_slots[-1].end_datetime.date() - reference_date.date()).days
396393
last_day = requested_tz.fromutc(reference_date + relativedelta(days=appointment_duration_days))
397394
elif self.category == 'punctual':
398395
# Punctual appointment type, the first day is the start_datetime if it is in the future, else the first day is now
399-
first_day = requested_tz.fromutc(self.start_datetime if self.start_datetime > now else now)
396+
first_day = requested_tz.fromutc(max(now, self.start_datetime))
400397
last_day = requested_tz.fromutc(self.end_datetime)
401398
else:
402399
# Recurring appointment type

appointment_capacity/models/calender_event.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from odoo import models, fields, api
2-
from odoo.exceptions import ValidationError
2+
33

44
class CalendarEvent(models.Model):
55
_inherit = "calendar.event"
@@ -15,13 +15,13 @@ def _inverse_resource_ids_or_capacity(self):
1515
# Ignore the inverse and keep the previous booking lines when we duplicate an event
1616
if self.env.context.get('is_appointment_copied'):
1717
continue
18-
if event.appointment_type_capacity_type != 'single_booking' and self.resource_total_capacity_reserved:
19-
capacity_to_reserve = self.resource_total_capacity_reserved
18+
if event.appointment_type_capacity_type != 'one_booking' and event.resource_total_capacity_reserved:
19+
capacity_to_reserve = event.resource_total_capacity_reserved
2020
else:
2121
capacity_to_reserve = sum(event.booking_line_ids.mapped('capacity_reserved')) or sum(resources.mapped('capacity'))
2222
event.booking_line_ids.sudo().unlink()
2323
for resource in resources.sorted("shareable"):
24-
if event.appointment_type_capacity_type != 'single_booking' and capacity_to_reserve <= 0:
24+
if event.appointment_type_capacity_type != 'one_booking' and capacity_to_reserve <= 0:
2525
break
2626
booking_lines.append({
2727
'appointment_resource_id': resource.id,

appointment_capacity/models/res_partner.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from odoo import models
22
from datetime import datetime, time
33

4+
45
class ResPartnerInherited(models.Model):
56
_inherit = 'res.partner'
67

78
def calendar_verify_availability(self, date_start, date_end):
8-
""" Verify availability of the partner(s) between 2 datetimes on their calendar.
9+
"""
10+
Verify availability of the partner(s) between 2 datetimes on their calendar.
911
We only verify events that are not linked to an appointment type with resources since
1012
someone could take multiple appointment for multiple resources. The availability of
1113
resources is managed separately by booking lines (see ``appointment.booking.line`` model)
@@ -26,7 +28,7 @@ def calendar_verify_availability(self, date_start, date_end):
2628
events_excluding_appointment_resource = all_events.filtered(lambda ev: ev.appointment_type_id.schedule_based_on != 'resources')
2729
for event in events_excluding_appointment_resource:
2830
if event.allday or (event.start < date_end and event.stop > date_start):
29-
if event.appointment_type_id and event.appointment_type_id.capacity_type != 'single_booking' and all(user in event.appointment_type_id.staff_user_ids.partner_id for user in self):
31+
if event.appointment_type_id and event.appointment_type_id.capacity_type != 'one_booking' and all(user in event.appointment_type_id.staff_user_ids.partner_id for user in self):
3032
continue
3133
if event.attendee_ids.filtered_domain(
3234
[('state', '!=', 'declined'),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_appointment_booking

0 commit comments

Comments
 (0)