1
|
|
|
""" |
2
|
|
|
byceps.services.seating.seating_area_service |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2014-2024 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from sqlalchemy import delete, select |
10
|
|
|
|
11
|
1 |
|
from byceps.database import db |
12
|
|
|
from byceps.services.party.models import PartyID |
13
|
1 |
|
from byceps.services.ticketing.dbmodels.ticket import DbTicket |
14
|
1 |
|
|
15
|
1 |
|
from .dbmodels.area import DbSeatingArea |
16
|
|
|
from .dbmodels.seat import DbSeat |
17
|
1 |
|
from .models import SeatingArea, SeatingAreaID, SeatUtilization |
18
|
1 |
|
|
19
|
1 |
|
|
20
|
|
|
def create_area( |
21
|
|
|
party_id: PartyID, |
22
|
1 |
|
slug: str, |
23
|
|
|
title: str, |
24
|
|
|
*, |
25
|
|
|
image_filename: str | None = None, |
26
|
|
|
image_width: int | None = None, |
27
|
|
|
image_height: int | None = None, |
28
|
|
|
) -> SeatingArea: |
29
|
|
|
"""Create an area.""" |
30
|
|
|
db_area = DbSeatingArea( |
31
|
|
|
party_id, |
32
|
1 |
|
slug, |
33
|
|
|
title, |
34
|
|
|
image_filename=image_filename, |
35
|
|
|
image_width=image_width, |
36
|
|
|
image_height=image_height, |
37
|
|
|
) |
38
|
|
|
|
39
|
|
|
db.session.add(db_area) |
40
|
|
|
db.session.commit() |
41
|
1 |
|
|
42
|
1 |
|
return _db_entity_to_area(db_area) |
43
|
|
|
|
44
|
1 |
|
|
45
|
|
|
def update_area( |
46
|
|
|
area_id: SeatingAreaID, |
47
|
1 |
|
slug: str, |
48
|
|
|
title: str, |
49
|
|
|
image_filename: str | None, |
50
|
|
|
image_width: int | None, |
51
|
|
|
image_height: int | None, |
52
|
|
|
) -> SeatingArea: |
53
|
|
|
"""Update an area.""" |
54
|
|
|
db_area = _find_db_area(area_id) |
55
|
|
|
|
56
|
|
|
if db_area is None: |
57
|
|
|
raise ValueError(f'Unknown seating area ID "{area_id}"') |
58
|
|
|
|
59
|
|
|
db_area.slug = slug |
60
|
|
|
db_area.title = title |
61
|
|
|
db_area.image_filename = image_filename |
62
|
|
|
db_area.image_width = image_width |
63
|
|
|
db_area.image_height = image_height |
64
|
|
|
|
65
|
|
|
db.session.commit() |
66
|
|
|
|
67
|
|
|
return _db_entity_to_area(db_area) |
68
|
|
|
|
69
|
|
|
|
70
|
|
|
def delete_area(area_id: SeatingAreaID) -> None: |
71
|
|
|
"""Delete an area.""" |
72
|
1 |
|
db.session.execute(delete(DbSeatingArea).filter_by(id=area_id)) |
73
|
|
|
db.session.commit() |
74
|
1 |
|
|
75
|
1 |
|
|
76
|
|
|
def count_areas_for_party(party_id: PartyID) -> int: |
77
|
|
|
"""Return the number of seating areas for that party.""" |
78
|
1 |
|
return ( |
79
|
|
|
db.session.scalar( |
80
|
1 |
|
select(db.func.count(DbSeatingArea.id)).filter_by(party_id=party_id) |
81
|
|
|
) |
82
|
|
|
or 0 |
83
|
|
|
) |
84
|
|
|
|
85
|
1 |
|
|
86
|
|
|
def find_area(area_id: SeatingAreaID) -> SeatingArea | None: |
87
|
|
|
"""Return the area, or `None` if not found.""" |
88
|
|
|
db_area = _find_db_area(area_id) |
89
|
|
|
|
90
|
|
|
if db_area is None: |
91
|
|
|
return None |
92
|
|
|
|
93
|
|
|
return _db_entity_to_area(db_area) |
94
|
|
|
|
95
|
1 |
|
|
96
|
|
|
def _find_db_area(area_id: SeatingAreaID) -> DbSeatingArea | None: |
97
|
|
|
return db.session.get(DbSeatingArea, area_id) |
98
|
|
|
|
99
|
1 |
|
|
100
|
|
|
def find_area_for_party_by_slug( |
101
|
|
|
party_id: PartyID, slug: str |
102
|
|
|
) -> SeatingArea | None: |
103
|
|
|
"""Return the area for that party with that slug, or `None` if not found.""" |
104
|
|
|
db_area = db.session.scalars( |
105
|
|
|
select(DbSeatingArea).filter_by(party_id=party_id).filter_by(slug=slug) |
106
|
|
|
).first() |
107
|
|
|
|
108
|
|
|
if db_area is None: |
109
|
|
|
return None |
110
|
|
|
|
111
|
|
|
return _db_entity_to_area(db_area) |
112
|
|
|
|
113
|
1 |
|
|
114
|
|
|
def get_areas_for_party(party_id: PartyID) -> list[SeatingArea]: |
115
|
|
|
"""Return all areas for that party.""" |
116
|
|
|
db_areas = db.session.scalars( |
117
|
|
|
select(DbSeatingArea).filter_by(party_id=party_id) |
118
|
|
|
).all() |
119
|
|
|
|
120
|
|
|
return [_db_entity_to_area(db_area) for db_area in db_areas] |
121
|
|
|
|
122
|
1 |
|
|
123
|
|
|
def get_areas_with_seat_utilization( |
124
|
|
|
party_id: PartyID, |
125
|
|
|
) -> list[tuple[SeatingArea, SeatUtilization]]: |
126
|
1 |
|
"""Return all areas and their seat utilization for that party.""" |
127
|
|
|
area = db.aliased(DbSeatingArea) |
128
|
1 |
|
|
129
|
|
|
subquery_occupied_seat_count = ( |
130
|
|
|
select(db.func.count(DbTicket.id)) |
131
|
|
|
.filter(DbTicket.revoked == False) # noqa: E712 |
132
|
|
|
.filter(DbTicket.occupied_seat_id.is_not(None)) |
133
|
|
|
.join(DbSeat) |
134
|
|
|
.filter(DbSeat.area_id == area.id) |
135
|
|
|
.scalar_subquery() |
136
|
|
|
) |
137
|
1 |
|
|
138
|
|
|
subquery_total_seat_count = ( |
139
|
|
|
select(db.func.count(DbSeat.id)) |
140
|
|
|
.filter_by(area_id=area.id) |
141
|
|
|
.scalar_subquery() |
142
|
|
|
) |
143
|
1 |
|
|
144
|
|
|
rows = db.session.execute( |
145
|
|
|
select( |
146
|
|
|
area, |
147
|
|
|
subquery_occupied_seat_count, |
148
|
|
|
subquery_total_seat_count, |
149
|
|
|
) |
150
|
|
|
.filter(area.party_id == party_id) |
151
|
|
|
.group_by(area.id) |
152
|
|
|
.order_by(area.title) |
153
|
|
|
).all() |
154
|
1 |
|
|
155
|
|
|
return [ |
156
|
|
|
( |
157
|
|
|
_db_entity_to_area(db_area), |
158
|
|
|
SeatUtilization( |
159
|
|
|
occupied=occupied_seat_count, total=total_seat_count |
160
|
|
|
), |
161
|
|
|
) |
162
|
|
|
for db_area, occupied_seat_count, total_seat_count in rows |
163
|
|
|
] |
164
|
|
|
|
165
|
1 |
|
|
166
|
1 |
|
def _db_entity_to_area(db_area: DbSeatingArea) -> SeatingArea: |
167
|
|
|
return SeatingArea( |
168
|
|
|
id=db_area.id, |
169
|
|
|
party_id=db_area.party_id, |
170
|
|
|
slug=db_area.slug, |
171
|
|
|
title=db_area.title, |
172
|
|
|
image_filename=db_area.image_filename, |
173
|
|
|
image_width=db_area.image_width, |
174
|
|
|
image_height=db_area.image_height, |
175
|
|
|
) |
176
|
|
|
|