Skip to content

Commit c5463fe

Browse files
authored
Merge pull request #234 from RieCo432/dev
#225 #226 #223 #224
2 parents 7b2b279 + 0790dac commit c5463fe

File tree

13 files changed

+174
-15
lines changed

13 files changed

+174
-15
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""#226 added ordering to appointment types
2+
3+
Revision ID: fa50787bdeca
4+
Revises: 29519339abb4
5+
Create Date: 2025-09-14 16:39:59.701066
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = 'fa50787bdeca'
16+
down_revision: Union[str, None] = '29519339abb4'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.add_column('appointmenttypes', sa.Column('orderindex', sa.Integer(), sa.Identity(always=False, start=1, cycle=True), nullable=False))
24+
op.create_index(op.f('ix_appointmenttypes_orderindex'), 'appointmenttypes', ['orderindex'], unique=True)
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade() -> None:
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.drop_index(op.f('ix_appointmenttypes_orderindex'), table_name='appointmenttypes')
31+
op.drop_column('appointmenttypes', 'orderindex')
32+
# ### end Alembic commands ###

fastapi/app/crud/appointments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def update_appointment_type(db: Session, appointment_type: models.AppointmentTyp
233233
return appointment_type
234234

235235

236-
def create_appointment_type(db: Session, appointment_type_data: schemas.AppointmentType) -> models.AppointmentType:
236+
def create_appointment_type(db: Session, appointment_type_data: schemas.AppointmentTypeCreate) -> models.AppointmentType:
237237
new_appointment_type = models.AppointmentType(
238238
id=appointment_type_data.id,
239239
title=appointment_type_data.title,

fastapi/app/crud/settings.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,24 @@ def swap_faq_order(db: Session, faq1_id: UUID, faq2_id: UUID) -> list[models.Faq
606606
except Exception as e:
607607
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail={"description": str(e)})
608608

609-
return [faq1, faq2]
609+
return [faq1, faq2]
610+
611+
612+
def swap_appointment_type_order(db: Session, type1_id: str, type2_id: str) -> list[models.AppointmentType]:
613+
type1 = get_appointment_type(db=db, appointment_type_id=type1_id)
614+
type2 = get_appointment_type(db=db, appointment_type_id=type2_id)
615+
616+
type1_old_order_index = type1.orderIndex
617+
type2_old_order_index = type2.orderIndex
618+
619+
try:
620+
type2.orderIndex = -1
621+
db.commit()
622+
type1.orderIndex = type2_old_order_index
623+
db.commit()
624+
type2.orderIndex = type1_old_order_index
625+
db.commit()
626+
except Exception as e:
627+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail={"description": str(e)})
628+
629+
return [type1, type2]

fastapi/app/models/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class AppointmentType(Base):
4747
description: Mapped[str] = mapped_column("description", Text, nullable=False, quote=False)
4848
duration: Mapped[int] = mapped_column("duration", Integer, nullable=False, quote=False)
4949

50+
orderIndex: Mapped[int] = mapped_column("orderindex", Integer, Identity(start=1, cycle=True), unique=True,
51+
index=True, nullable=False, quote=False)
52+
5053
def __eq__(self, other: dict):
5154
return all([
5255
str(self.id) == str(other.get("id")),

fastapi/app/routers/appointments.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from datetime import datetime
2+
from typing import Annotated
23
from uuid import UUID
34
from dateutil import relativedelta
4-
from fastapi import APIRouter, Depends, BackgroundTasks
5+
from fastapi import APIRouter, Depends, BackgroundTasks, Body, HTTPException
56
from sqlalchemy.orm import Session
7+
from starlette import status
8+
69
import app.crud as crud
710
import app.dependencies as dep
811
import app.schemas as schemas
@@ -59,10 +62,22 @@ async def cancel_appointment(
5962

6063

6164
@appointments.post("/appointments/types")
62-
async def create_appointment_type(new_appointment_type: schemas.AppointmentType, db: Session = Depends(dep.get_db)) -> schemas.AppointmentType:
65+
async def create_appointment_type(new_appointment_type: schemas.AppointmentTypeCreate, db: Session = Depends(dep.get_db)) -> schemas.AppointmentType:
6366
return crud.create_appointment_type(db=db, appointment_type_data=new_appointment_type)
6467

6568

69+
@appointments.patch("/settings/appointments/swap")
70+
async def patch_swap_faq(
71+
appointment_type_1_id: Annotated[str, Body(embed=True)],
72+
appointment_type_2_id: Annotated[str, Body(embed=True)],
73+
db: Session = Depends(dep.get_db)
74+
) -> list[schemas.AppointmentType]:
75+
if crud.get_appointment_type(db=db, appointment_type_id=appointment_type_1_id) is None or crud.get_appointment_type(db=db, appointment_type_id=appointment_type_2_id) is None:
76+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail={"description": "One or more Appointment Types not found."})
77+
78+
return crud.swap_appointment_type_order(db=db, type1_id=appointment_type_1_id, type2_id=appointment_type_2_id)
79+
80+
6681
@appointments.patch("/appointments/types/{type_id}")
6782
async def update_appointment_type(
6883
type_id: str,

fastapi/app/schemas/settings.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33

44
from pydantic import BaseModel, ConfigDict
55

6-
7-
class AppointmentType(BaseModel):
6+
class AppointmentTypeCreate(BaseModel):
87
id: str
98
active: bool
109
title: str
1110
description: str
1211
duration: int
1312

13+
class AppointmentType(AppointmentTypeCreate):
14+
orderIndex: int
15+
1416

1517
class PatchAppointmentType(BaseModel):
1618
active: bool | None = None

fastapi/app/services/email_helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ def build_crime_report_added_email(crime_report: schemas.CrimeReportFull):
351351
"</p>"
352352
" </body>"
353353
"</html>".format(crime_report.crimeNumber,
354-
crime_report.createdOn.strftime("%dd/%mmm/%YYYY"),
354+
crime_report.createdOn.strftime("%d %B %Y"),
355355
crime_report.contract.client.firstName, crime_report.contract.client.lastName,
356356
crime_report.contract.bike.make, crime_report.contract.bike.model,
357357
crime_report.contract.bike.colour,
@@ -391,8 +391,8 @@ def build_crime_report_closed_email(crime_report: schemas.CrimeReportFull):
391391
"</p>"
392392
" </body>"
393393
"</html>".format(crime_report.crimeNumber,
394-
crime_report.createdOn.strftime("%dd/%mmm/%YYYY"),
395-
crime_report.closedOn.strftime("%dd/%mmm/%YYYY"),
394+
crime_report.createdOn.strftime("%d %B %Y"),
395+
crime_report.closedOn.strftime("%d %B %Y"),
396396
crime_report.contract.client.firstName, crime_report.contract.client.lastName,
397397
crime_report.contract.bike.make, crime_report.contract.bike.model,
398398
crime_report.contract.bike.colour,

vuejs/src/requests/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,4 +1430,16 @@ export default {
14301430
validateStatus: (status) => validateCommonHTTPErrorCodes(status, {userLoginRequired: true}),
14311431
});
14321432
},
1433+
swapAppointmentTypes(type1Id, type2Id) {
1434+
return axiosClient.patch('/settings/appointments/swap',
1435+
{
1436+
appointment_type_1_id: type1Id,
1437+
appointment_type_2_id: type2Id,
1438+
},
1439+
{
1440+
headers: credentialsStore.getApiRequestHeader(),
1441+
validateStatus: (status) => validateCommonHTTPErrorCodes(status, {userLoginRequired: true}),
1442+
},
1443+
);
1444+
},
14331445
};

vuejs/src/views/admin/appointmentTypes.vue

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,42 @@ export default {
114114
},
115115
);
116116
},
117+
moveUpAppointmentType(appointmentTypeId) {
118+
const indexInArray = this.appointmentTypes.findIndex((appointmentType) => (appointmentType.id === appointmentTypeId));
119+
if (indexInArray === 0) {
120+
toast.error('This is the first appointment type', {timeout: 2000});
121+
return;
122+
}
123+
const appointmentTypeToMoveUp = this.appointmentTypes[indexInArray];
124+
const appointmentTypeToMoveDown = this.appointmentTypes[indexInArray - 1];
125+
requests.swapAppointmentTypes(appointmentTypeToMoveUp.id, appointmentTypeToMoveDown.id)
126+
.then((response) => {
127+
this.appointmentTypes.splice(indexInArray - 1, 2, response.data[0], response.data[1]);
128+
this.appointmentTypes.sort((a, b) => (a.orderIndex - b.orderIndex));
129+
toast.success('Appointment Type moved', {timeout: 2000});
130+
})
131+
.catch((error) => {
132+
toast.error(error.response.data.detail.description, {timeout: 2000});
133+
});
134+
},
135+
moveDownAppointmentType(appointmentTypeId) {
136+
const indexInArray = this.appointmentTypes.findIndex((appointmentType) => (appointmentType.id === appointmentTypeId));
137+
if (indexInArray === this.appointmentTypes.length - 1) {
138+
toast.error('This is the last appointment type', {timeout: 2000});
139+
return;
140+
}
141+
const appointmentTypeToMoveDown = this.appointmentTypes[indexInArray];
142+
const appointmentTypeToMoveUp = this.appointmentTypes[indexInArray + 1];
143+
requests.swapAppointmentTypes(appointmentTypeToMoveDown.id, appointmentTypeToMoveUp.id)
144+
.then((response) => {
145+
this.appointmentTypes.splice(indexInArray, 2, response.data[0], response.data[1]);
146+
this.appointmentTypes.sort((a, b) => (a.orderIndex - b.orderIndex));
147+
toast.success('Appointment Type moved', {timeout: 2000});
148+
})
149+
.catch((error) => {
150+
toast.error(error.response.data.detail.description, {timeout: 2000});
151+
});
152+
},
117153
patchAppointmentType(appointmentTypeId, patchData) {
118154
requests.patchAppointmentType(appointmentTypeId, patchData)
119155
.then((response) => {
@@ -136,6 +172,16 @@ export default {
136172
icon: 'heroicons-outline:pencil-square',
137173
func: this.openEditAppointmentTypeModal,
138174
},
175+
{
176+
label: 'Move Up',
177+
icon: 'heroicons-outline:arrow-up',
178+
func: this.moveUpAppointmentType,
179+
},
180+
{
181+
label: 'Move Down',
182+
icon: 'heroicons-outline:arrow-down',
183+
func: this.moveDownAppointmentType,
184+
},
139185
],
140186
appointmentTypesColumns: [
141187
{
@@ -167,7 +213,7 @@ export default {
167213
},
168214
created() {
169215
requests.getAppointmentTypes(true).then((response) => {
170-
this.appointmentTypes = response.data;
216+
this.appointmentTypes = response.data.sort((a, b) => (a.orderIndex - b.orderIndex));
171217
this.loadingAppointmentTypes = false;
172218
});
173219
},

vuejs/src/views/appointments/clientBookAppointment.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export default {
102102
created() {
103103
requests.getClientMe(); // if this returns 401 the client will get redirected to login page
104104
requests.getAppointmentTypes().then((response) => {
105-
this.appointmentTypes = response.data;
105+
this.appointmentTypes = response.data.sort((a, b) => (a.orderIndex - b.orderIndex));
106106
});
107107
},
108108
};
@@ -231,7 +231,8 @@ export default {
231231

232232
<template v-else>
233233
<h4 class="text-base text-slate-800 dark:text-slate-300 mb-6">
234-
Sorry, there are no appointments available at this time. Please check again later.
234+
Unfortunately, there are currently no available slots for this appointment type due to high demand.
235+
More slots will be added in a few days. Please try again in a few days.
235236
</h4>
236237
</template>
237238

0 commit comments

Comments
 (0)